From 0fba760449915c7e0828f1924fe52d43e134833d Mon Sep 17 00:00:00 2001 From: icpp Date: Fri, 29 Mar 2024 17:16:49 -0400 Subject: [PATCH] Refactor: remove all traps; return Result of Record nft_get_story returns a StoryRecordResult . InferenceRecordResult returned by inference, nft_story_xxx . update scripts for InferenceRecordResult Remove all references to charles Remove trap calls Refactor trap in set_canister_mode initialize returns StatusCodeRecordResult , and does not trap nft_whitelist, nft_init, nft_mint: no trap; StatusCodeRecordResult nft_metadata; no trap; NFTCollectionRecordResult no trap in get_users; UsersRecordResult fix pytest no trap in get_user_metadata StatusCodeRecordResult everywhere; No more traps Return Err when malloc fails fix pytest . . --- icpp_llama2/README.md | 3 + icpp_llama2/canister_ids.json | 3 - icpp_llama2/demo.sh | 19 +- icpp_llama2/demo_pytest.sh | 83 ++++ icpp_llama2/dfx.json | 5 - icpp_llama2/native/main.cpp | 387 ++++++++++-------- icpp_llama2/scripts/ic_py_canister.py | 17 +- icpp_llama2/scripts/nft_init.py | 4 +- icpp_llama2/scripts/nft_metadata.py | 4 +- icpp_llama2/scripts/nft_mint.py | 4 +- icpp_llama2/scripts/nft_update_story.py | 6 +- icpp_llama2/scripts/parse_args_nft_init.py | 6 + .../scripts/parse_args_nft_metadata.py | 6 + icpp_llama2/scripts/parse_args_nft_mint.py | 6 + icpp_llama2/scripts/parse_args_upload.py | 6 + icpp_llama2/scripts/upload.py | 4 +- icpp_llama2/src/canister.cpp | 28 +- icpp_llama2/src/chats.cpp | 32 +- icpp_llama2/src/chats.h | 4 +- icpp_llama2/src/inference.cpp | 93 +++-- icpp_llama2/src/inference.h | 5 +- icpp_llama2/src/initialize.cpp | 79 ++-- icpp_llama2/src/llama2.did | 134 ++++-- icpp_llama2/src/nft_collection.cpp | 205 +++++++--- icpp_llama2/src/nft_collection.h | 2 + icpp_llama2/src/run.c | 16 +- icpp_llama2/src/run.h | 2 +- icpp_llama2/src/upload.cpp | 48 ++- icpp_llama2/src/users.cpp | 41 +- icpp_llama2/src/users.h | 3 +- icpp_llama2/test/test_apis.py | 66 +-- 31 files changed, 896 insertions(+), 425 deletions(-) create mode 100755 icpp_llama2/demo_pytest.sh diff --git a/icpp_llama2/README.md b/icpp_llama2/README.md index 066408c..368ad58 100644 --- a/icpp_llama2/README.md +++ b/icpp_llama2/README.md @@ -36,6 +36,9 @@ Invoke-WebRequest -Uri https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin -OutFile .\models\stories15M.bin ``` +- The demo_pytest.sh script starts the local network, deploys llama2_260K, uploads the model & tokenizer, and runs the QA with pytest: + - `./demo_pytest.sh` , on Linux / Mac + - The *demo* script starts the local network, deploys llama2, uploads the model & tokenizer, and generates two stories: - `./demo.sh` , on Linux / Mac - `.\demo.ps1` , in Windows PowerShell (Miniconda recommended) diff --git a/icpp_llama2/canister_ids.json b/icpp_llama2/canister_ids.json index 722fdc5..1d85e23 100644 --- a/icpp_llama2/canister_ids.json +++ b/icpp_llama2/canister_ids.json @@ -1,7 +1,4 @@ { - "charles": { - "ic": "lkh5o-3yaaa-aaaag-acguq-cai" - }, "llama2": { "ic": "4c4bn-daaaa-aaaag-abvcq-cai" }, diff --git a/icpp_llama2/demo.sh b/icpp_llama2/demo.sh index 920bb59..058347e 100755 --- a/icpp_llama2/demo.sh +++ b/icpp_llama2/demo.sh @@ -24,14 +24,30 @@ icpp build-wasm --to-compile all echo " " echo "--------------------------------------------------" echo "Deploying the wasm to a canister on the local network" -dfx deploy +dfx deploy llama2_260K +dfx deploy llama2 + +####################################################################### +echo " " +echo "--------------------------------------------------" +echo "Setting canister_mode to chat-principal" +dfx canister call llama2_260K set_canister_mode chat-principal +dfx canister call llama2 set_canister_mode chat-principal ####################################################################### echo " " echo "--------------------------------------------------" echo "Checking health endpoint" +dfx canister call llama2_260K health dfx canister call llama2 health +####################################################################### +echo " " +echo "--------------------------------------------------" +echo "Initializing the canister configurations" +python -m scripts.nft_init --network local --canister llama2_260K --nft-supply-cap 0 --nft-symbol "" --nft-name "" --nft-description "" +python -m scripts.nft_init --network local --canister llama2 --nft-supply-cap 0 --nft-symbol "" --nft-name "" --nft-description "" + ####################################################################### echo " " echo "--------------------------------------------------" @@ -43,6 +59,7 @@ python -m scripts.upload --canister llama2 --model models/stories15M.bin --token echo " " echo "--------------------------------------------------" echo "Checking readiness endpoint" +dfx canister call llama2_260K ready dfx canister call llama2 ready ####################################################################### diff --git a/icpp_llama2/demo_pytest.sh b/icpp_llama2/demo_pytest.sh new file mode 100755 index 0000000..8e7032a --- /dev/null +++ b/icpp_llama2/demo_pytest.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +dfx identity use default + +####################################################################### +# For Linux & Mac +####################################################################### + +echo " " +echo "--------------------------------------------------" +echo "Stopping the local network" +dfx stop + +echo " " +echo "--------------------------------------------------" +echo "Starting the local network as a background process" +dfx start --clean --background + +####################################################################### +echo "--------------------------------------------------" +echo "Building the wasm with wasi-sdk" +# icpp build-wasm --to-compile all +icpp build-wasm --to-compile mine + +####################################################################### +echo " " +echo "--------------------------------------------------" +echo "Deploying the wasm to a canister on the local network" +dfx deploy llama2_260K + +####################################################################### +echo " " +echo "--------------------------------------------------" +echo "Setting canister_mode to chat-principal" +dfx canister call llama2_260K set_canister_mode chat-principal + +####################################################################### +echo " " +echo "--------------------------------------------------" +echo "Checking health endpoint" +dfx canister call llama2_260K health + +####################################################################### +echo " " +echo "--------------------------------------------------" +echo "Initializing the canister configurations" +python -m scripts.nft_init --network local --canister llama2_260K --nft-supply-cap 0 --nft-symbol "" --nft-name "" --nft-description "" + +####################################################################### +echo " " +echo "--------------------------------------------------" +echo "Uploading the model & tokenizer" +python -m scripts.upload --network local --canister llama2_260K --model stories260K/stories260K.bin --tokenizer stories260K/tok512.bin + +####################################################################### +echo " " +echo "--------------------------------------------------" +echo "Checking readiness endpoint" +dfx canister call llama2_260K ready + +####################################################################### +echo " " +echo "--------------------------------------------------" +echo "Running the full smoketests with pytest" +pytest --network=local + +####################################################################### +# echo "--------------------------------------------------" +# echo "Stopping the local network" +# dfx stop + +# ####################################################################### +# echo " " +# echo "--------------------------------------------------" +# echo "Building the OS native debug executable with clang++" +# icpp build-native --to-compile all +# # icpp build-native --to-compile mine + +# ####################################################################### +# echo " " +# echo "--------------------------------------------------" +# echo "Running the OS native debug executable" +# ./build-native/mockic.exe \ No newline at end of file diff --git a/icpp_llama2/dfx.json b/icpp_llama2/dfx.json index 5fcd8a9..6a6b74d 100644 --- a/icpp_llama2/dfx.json +++ b/icpp_llama2/dfx.json @@ -20,11 +20,6 @@ "type": "custom", "candid": "src/llama2.did", "wasm": "build/llama2.wasm" - }, - "charles": { - "type": "custom", - "candid": "src/llama2.did", - "wasm": "build/llama2.wasm" } }, "defaults": { diff --git a/icpp_llama2/native/main.cpp b/icpp_llama2/native/main.cpp index bf5ce7d..223eca2 100644 --- a/icpp_llama2/native/main.cpp +++ b/icpp_llama2/native/main.cpp @@ -184,10 +184,21 @@ int main() { // # canister_mode = 'chat-principal' # // #################################### - // '("chat-principal": text)' -> '()' - mockIC.run_test("set_canister_mode to chat-principal", set_canister_mode, - "4449444c0001710e636861742d7072696e636970616c", - "4449444c0000", silent_on_trap, my_principal); + // '("wrong-mode": text)' + // -> '(variant { Err = "ERROR: unknown canister_mode = wrong-mode" : text})' + mockIC.run_test( + "set_canister_mode to wrong-mode", set_canister_mode, + "4449444c0001710a77726f6e672d6d6f6465", + "4449444c026b01b0ad8fcd0c716b01c5fed2010001010000294552524f523a20756e6b6e6f776e2063616e69737465725f6d6f6465203d2077726f6e672d6d6f6465", + silent_on_trap, my_principal); + + // '("chat-principal": text)' + // -> '(variant { Ok = record { canister_mode = "chat-principal"} })' + mockIC.run_test( + "set_canister_mode to chat-principal", set_canister_mode, + "4449444c0001710e636861742d7072696e636970616c", + "4449444c026c01bbbff4b30c716b01bc8a01000101000e636861742d7072696e636970616c", + silent_on_trap, my_principal); // ----------------------------------------------------------------------------- // The canister health & readiness checks @@ -207,17 +218,18 @@ int main() { "4449444c0001713f6578706d742d67747873772d696e66746a2d747461626a2d71687035732d6e6f7a75702d6e3362626f2d6b377a766e2d64673468652d6b6e6163332d6c6165", silent_on_trap, my_principal); - // '()' -> '(variant { Err = 401 : nat16 })' + // '()' -> '(variant { Err = record { Other = "Access Denied"} })' // Call with non owner principal must be denied - mockIC.run_test("reset_model Err", reset_model, "4449444c0000", - "4449444c016b01c5fed2017a0100009101", silent_on_trap, - anonymous_principal); + mockIC.run_test( + "reset_model Err", reset_model, "4449444c0000", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100000d4163636573732044656e696564", + silent_on_trap, anonymous_principal); // '()' -> '(variant { Ok = 200 : nat16 })' // Call with owner principal mockIC.run_test("reset_model", reset_model, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // ========================================================================== std::cout << "\n+++++++++++++++++++++++++++++++++++++++++++++++++++++\n"; @@ -257,7 +269,7 @@ int main() { << candid_in.substr(candid_in.size() - 10) << "\n"; // candid_in -> '(variant { Ok = 200 : nat16 })' - candid_out_expected = "4449444c016b01bc8a017a010000c800"; + candid_out_expected = "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800"; mockIC.run_test("upload_model_bytes_chunk", upload_model_bytes_chunk, candid_in, candid_out_expected, silent_on_trap, my_principal); @@ -304,7 +316,7 @@ int main() { << candid_in.substr(candid_in.size() - 10) << "\n"; // candid_in -> '(variant { Ok = 200 : nat16 })' - candid_out_expected = "4449444c016b01bc8a017a010000c800"; + candid_out_expected = "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800"; mockIC.run_test("upload_tokenizer_bytes_chunk", upload_tokenizer_bytes_chunk, candid_in, candid_out_expected, silent_on_trap, my_principal); @@ -315,19 +327,19 @@ int main() { // ========================================================================== // Mimic two principals at once are building a story... - // '()' -> '(variant { Ok = 200 : nat16 })' + // '()' -> '(variant { Ok = record { status_code = 200 : nat16} })' mockIC.run_test("initialize", initialize, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - your_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, your_principal); // '()' -> '(variant { Err = "The Llama2 canister does not allow calling with anonymous principal." : text })' mockIC.run_test( @@ -418,8 +430,8 @@ int main() { // A new chat, using a prompt built in multiple steps // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, principal); } // Loop to create a long story, 10 tokens at a time @@ -447,16 +459,21 @@ int main() { mockIC.run_test("inference 0a", inference, candid_in, "", silent_on_trap, principals[j], &candid_out); + CandidTypeRecord inference_record; + inference_record.append("inference", + CandidTypeText{&generated_tokens[j]}); std::string err_text; CandidTypeVariant v_out; - v_out.append("Ok", CandidTypeText(&generated_tokens[j])); - v_out.append("Err", CandidTypeText(&err_text)); + v_out.append("Ok", inference_record); + v_out.append("Err", + CandidTypeVariant{"Other", CandidTypeText(&err_text)}); CandidArgs A; A.append(v_out); CandidDeserialize(candid_out, A); if (err_text.size() > 0) { - std::cout << "ERROR returned by inference function.\n"; + std::cout << "Err returned by inference function:\n" + << err_text << "\n"; exit(1); } story[j] += generated_tokens[j]; @@ -490,8 +507,8 @@ int main() { { // A new chat, starting with an empty prompt // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - your_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, your_principal); // Loop to create 1000 token long story, 10 tokens at a time // With temperature=0.0: greedy argmax sampling -> the story will be the same every time @@ -516,10 +533,18 @@ int main() { mockIC.run_test("inference 0b", inference, candid_in, "", silent_on_trap, your_principal, &candid_out); + CandidTypeRecord inference_record; + inference_record.append("inference", CandidTypeText{&generated_tokens}); std::string err_text; CandidTypeVariant v_out; - v_out.append("Ok", CandidTypeText(&generated_tokens)); - v_out.append("Err", CandidTypeText(&err_text)); + v_out.append("Ok", inference_record); + v_out.append("Err", + CandidTypeVariant{"Other", CandidTypeText(&err_text)}); + + // std::string err_text; + // CandidTypeVariant v_out; + // v_out.append("Ok", CandidTypeText(&generated_tokens)); + // v_out.append("Err", CandidTypeText(&err_text)); CandidArgs A; A.append(v_out); @@ -539,28 +564,19 @@ int main() { // A new chat // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // With temperature=0.0: greedy argmax sampling -> the story will be the same every time // '(record {prompt = "" : text; steps = 100 : nat64; temperature = 0.0 : float32; topp = 1.0 : float32; rng_seed = 0 : nat64;})' + // -> '(variant { Ok = record { inference = "...story..." : text;} })' expected_response = "-to-do-B-"; if (model_to_use == 1) { - // -> '(variant { Ok = ""Once upon a time, there was a little girl named Lily. She loved to play outside in the park. One day, she saw a big, red ball. She wanted to play with it, but it was too high.\nLily\'s mom said, \"Lily, let\'s go to the park.\" Lily was sad and didn\'t know w\n"" : text })' expected_response = - "4449444c016b01bc8a0171010000fd014f6e63652075706f6e20612074696d652c207468657265207761732061206c6974746c65206769726c206e616d6564204c696c792e20536865206c6f76656420746f20706c6179206f75747369646520696e20746865207061726b2e204f6e65206461792c20736865207361772061206269672c207265642062616c6c2e205368652077616e74656420746f20706c617920776974682069742c206275742069742077617320746f6f20686967682e0a4c696c792773206d6f6d20736169642c20224c696c792c206c6574277320676f20746f20746865207061726b2e22204c696c79207761732073616420616e64206469646e2774206b6e6f772077"; + "4449444c026c01d9b3b9980f716b01bc8a0100010100fd014f6e63652075706f6e20612074696d652c207468657265207761732061206c6974746c65206769726c206e616d6564204c696c792e20536865206c6f76656420746f20706c6179206f75747369646520696e20746865207061726b2e204f6e65206461792c20736865207361772061206269672c207265642062616c6c2e205368652077616e74656420746f20706c617920776974682069742c206275742069742077617320746f6f20686967682e0a4c696c792773206d6f6d20736169642c20224c696c792c206c6574277320676f20746f20746865207061726b2e22204c696c79207761732073616420616e64206469646e2774206b6e6f772077"; } else if (model_to_use == 2) { - // -> '(variant { Ok = "Once upon a time, there was a little girl named Lily. She loved to play outside in the sunshine. One day, she saw a big, red ball in the sky. It was the sun! She thought it was so pretty.\nLily wanted to play with the ball, but it was too high up in the sky. She tried to jump and reach it, but she couldn\'t. Then, she had an idea. She would use a stick to knock the" : text })' - expected_response = - "4449444c016b019cc20171010000ed024f6e63652075706f6e20612074696d652c207468657265207761732061206c6974746c65206769726c206e616d6564204c696c792e20536865206c6f76656420746f20706c6179206f75747369646520696e207468652073756e7368696e652e204f6e65206461792c20736865207361772061206269672c207265642062616c6c20696e2074686520736b792e20497420776173207468652073756e21205368652074686f756768742069742077617320736f207072657474792e0a4c696c792077616e74656420746f20706c61792077697468207468652062616c6c2c206275742069742077617320746f6f206869676820757020696e2074686520736b792e2053686520747269656420746f206a756d7020616e642072656163682069742c206275742073686520636f756c646e27742e205468656e2c207368652068616420616e20696465612e2053686520776f756c6420757365206120737469636b20746f206b6e6f636b20746865"; } else if (model_to_use == 3) { - // -> '(variant { Ok = "Once upon a time, there was a little girl named Lily. She loved to play outside in the sunshine. One day, she saw a big, yellow flower in the garden. It was a sunflower! Lily thought it was the most beautiful flower she had ever seen.\nLily\'s mom came outside and saw the sunflower too. \"Wow, that\'s a big flower!\" she said. \"Let\'s pick it and put it in" : text })' - expected_response = - "4449444c016b019cc20171010000e0024f6e63652075706f6e20612074696d652c207468657265207761732061206c6974746c65206769726c206e616d6564204c696c792e20536865206c6f76656420746f20706c6179206f75747369646520696e207468652073756e7368696e652e204f6e65206461792c20736865207361772061206269672c2079656c6c6f7720666c6f77657220696e207468652067617264656e2e2049742077617320612073756e666c6f77657221204c696c792074686f756768742069742077617320746865206d6f73742062656175746966756c20666c6f77657220736865206861642065766572207365656e2e0a4c696c792773206d6f6d2063616d65206f75747369646520616e6420736177207468652073756e666c6f77657220746f6f2e2022576f772c2074686174277320612062696720666c6f77657221222073686520736169642e20224c65742773207069636b20697420616e642070757420697420696e"; } else if (model_to_use == 4) { - // -> '(variant { Ok = "Once upon a time, there was a little girl named Lily. She loved to play outside in the sunshine. One day, she saw a big, red apple on a tree. She wanted to eat it, but it was too high up.\nLily asked her friend, a little bird, \"Can you help me get the apple?\"\nThe bird said, \"Sure, I can fly up and get it for you.\"\nThe bird flew up to the apple" : text })' - expected_response = - "4449444c016b019cc20171010000d8024f6e63652075706f6e20612074696d652c207468657265207761732061206c6974746c65206769726c206e616d6564204c696c792e20536865206c6f76656420746f20706c6179206f75747369646520696e207468652073756e7368696e652e204f6e65206461792c20736865207361772061206269672c20726564206170706c65206f6e206120747265652e205368652077616e74656420746f206561742069742c206275742069742077617320746f6f20686967682075702e0a4c696c792061736b65642068657220667269656e642c2061206c6974746c6520626972642c202243616e20796f752068656c70206d652067657420746865206170706c653f220a546865206269726420736169642c2022537572652c20492063616e20666c7920757020616e642067657420697420666f7220796f752e220a546865206269726420666c657720757020746f20746865206170706c65"; } mockIC.run_test( "inference 1", inference, @@ -571,8 +587,8 @@ int main() { // A new chat // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // With temperature=0.0 & topp=0.9, still greedy argmax sampling -> the story will be the same every time // '(record {prompt = "" : text; steps = 100 : nat64; temperature = 0.0 : float32; topp = 0.9 : float32; rng_seed = 0 : nat64;})' mockIC.run_test( @@ -584,8 +600,8 @@ int main() { // A new chat // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // With temperature>0.0 & topp=1.0: regular sampling // '(record {prompt = "" : text; steps = 100 : nat64; temperature = 0.9 : float32; topp = 1.0 : float32; rng_seed = 0 : nat64;})' // -> --can not check on story-- @@ -598,8 +614,8 @@ int main() { // A new chat // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // With temperature>0.0 & topp<1.0: top-pp (nucleus) sampling // '(record {prompt = "" : text; steps = 100 : nat64; temperature = 0.9 : float32; topp = 0.9 : float32; rng_seed = 0 : nat64;})' // -> --can not check on story-- @@ -612,8 +628,8 @@ int main() { // A new chat // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // With temperature=0.0: greedy argmax sampling -> the story will be the same every time // '(record {prompt = "Yesterday I went for a walk" : text; steps = 100 : nat64; temperature = 0.0 : float32; topp = 1.0 : float32; rng_seed = 0 : nat64;})' // -> '(variant { Ok = "..." : text })' @@ -626,8 +642,8 @@ int main() { // A new chat // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // With temperature=0.0 & topp=0.9, still greedy argmax sampling -> the story will be the same every time // '(record {prompt = "Yesterday I went for a walk" : text; steps = 100 : nat64; temperature = 0.0 : float32; topp = 0.9 : float32; rng_seed = 0 : nat64;})' // -> '(variant { Ok = "..." : text })' @@ -640,8 +656,8 @@ int main() { // A new chat // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // With temperature>0.0 & topp=1.0: regular sampling // '(record {prompt = "Yesterday I went for a walk" : text; steps = 100 : nat64; temperature = 0.9 : float32; topp = 1.0 : float32; rng_seed = 0 : nat64;})' // -> --can not check on story-- @@ -654,8 +670,8 @@ int main() { // A new chat // '()' -> '(variant { Ok = 200 : nat16 })' mockIC.run_test("new_chat", new_chat, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // With temperature>0.0 & topp<1.0: top-pp (nucleus) sampling // '(record {prompt = "Yesterday I went for a walk" : text; steps = 100 : nat64; temperature = 0.9 : float32; topp = 0.9 : float32; rng_seed = 0 : nat64;})' // -> --can not check on story-- @@ -668,64 +684,73 @@ int main() { // # canister_mode = 'nft-ordinal' # // ################################# - // '("nft-ordinal": text)' -> '()' - mockIC.run_test("set_canister_mode to nft-ordinal", set_canister_mode, - "4449444c0001710b6e66742d6f7264696e616c", "4449444c0000", - silent_on_trap, my_principal); + // '("nft-ordinal": text)' + // -> '(variant { Ok = record { canister_mode = "nft-ordinal"} })' + mockIC.run_test( + "set_canister_mode to nft-ordinal", set_canister_mode, + "4449444c0001710b6e66742d6f7264696e616c", + "4449444c026c01bbbff4b30c716b01bc8a01000101000b6e66742d6f7264696e616c", + silent_on_trap, my_principal); // ----------------------------------------------------------------------------- // Whitelist principals that can mint NFTs // '(record { id = principal "nfxu4-cn7qt-x7r3c-5dhnk-dcrct-gmgoz-67gcg-5glvc-2krhv-gcmsr-qqe"; description = "A whitelisted user"; })' - // -> TRAP + // -> '(variant { Err = record { Other = "Access Denied"} })' // Call with non owner principal must be denied - mockIC.run_trap_test( + mockIC.run_test( "nft_whitelist a user as non-owner", nft_whitelist, "4449444c016c02dbb70168fc91f4f805710100011dbf84eff8ec5d19daa18a2299986767df308dd32ea2d2a27a984c94610212412077686974656c69737465642075736572", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100000d4163636573732044656e696564", silent_on_trap, your_principal); // '(record { id = principal "nfxu4-cn7qt-x7r3c-5dhnk-dcrct-gmgoz-67gcg-5glvc-2krhv-gcmsr-qqe"; description = "A whitelisted user"; })' - // -> '(variant { Ok = 200 : nat16 })' + // -> '(variant { Ok = record { status_code = 200 : nat16} })' mockIC.run_test( "nft_whitelist a user", nft_whitelist, "4449444c016c02dbb70168fc91f4f805710100011dbf84eff8ec5d19daa18a2299986767df308dd32ea2d2a27a984c94610212412077686974656c69737465642075736572", - "4449444c016b01bc8a017a010000c800", silent_on_trap, my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", silent_on_trap, + my_principal); // '(record { id = principal "bd3sg-teaaa-aaaaa-qaaba-cai"; description = "A whitelisted canister"; })' - // -> '(variant { Ok = 200 : nat16 })' + // -> '(variant { Ok = record { status_code = 200 : nat16} })' mockIC.run_test( "nft_whitelist a canister", nft_whitelist, "4449444c016c02dbb70168fc91f4f805710100010a8000000000100002010116412077686974656c69737465642063616e6973746572", - "4449444c016b01bc8a017a010000c800", silent_on_trap, my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", silent_on_trap, + my_principal); // ----------------------------------------------------------------------------- // Initialize the NFT Collection // '(record { nft_supply_cap = 2 : nat64; nft_total_supply = 0 : nat64; nft_symbol = "CHARLES"; nft_name = "Sir Charles The 3rd"; nft_description = "A Bitcoin Ordinal Collection powered by a C++ LLM running on the Internet Computer"})' - // -> TRAP + // -> '(variant { Err = record { Other = "Access Denied"} })' // Call with non owner principal must be denied - mockIC.run_trap_test( + mockIC.run_test( "nft_init as non-owner", nft_init, "4449444c016c05a5d0bfc60278ee86c3950871bb9effa20f71b9e4b5ac0f71ad91e7ca0f78010002000000000000001353697220436861726c6573205468652033726407434841524c4553524120426974636f696e204f7264696e616c20436f6c6c656374696f6e20706f7765726564206279206120432b2b204c4c4d2072756e6e696e67206f6e2074686520496e7465726e657420436f6d70757465720000000000000000", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100000d4163636573732044656e696564", silent_on_trap, your_principal); - // -> '(variant { Ok = 200 : nat16 })' + // -> '(variant { Ok = record { status_code = 200 : nat16} })' mockIC.run_test( "nft_init", nft_init, "4449444c016c05a5d0bfc60278ee86c3950871bb9effa20f71b9e4b5ac0f71ad91e7ca0f78010002000000000000001353697220436861726c6573205468652033726407434841524c4553524120426974636f696e204f7264696e616c20436f6c6c656374696f6e20706f7765726564206279206120432b2b204c4c4d2072756e6e696e67206f6e2074686520496e7465726e657420436f6d70757465720000000000000000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", silent_on_trap, + my_principal); - // -> TRAP + // -> '(variant { Err = record { Other = "NFT Collection is already initialized"} })' // Re-initialization must be denied - mockIC.run_trap_test( + mockIC.run_test( "nft_init again", nft_init, "4449444c016c05a5d0bfc60278ee86c3950871bb9effa20f71b9e4b5ac0f71ad91e7ca0f78010002000000000000001353697220436861726c6573205468652033726407434841524c4553524120426974636f696e204f7264696e616c20436f6c6c656374696f6e20706f7765726564206279206120432b2b204c4c4d2072756e6e696e67206f6e2074686520496e7465726e657420436f6d70757465720000000000000000", + "4449444c026b01b0ad8fcd0c716b01c5fed2010001010000254e465420436f6c6c656374696f6e20697320616c726561647920696e697469616c697a6564", silent_on_trap, my_principal); - // '()' -> '(record { nft_supply_cap = 2 : nat64; nft_total_supply = 0 : nat64; nft_symbol = "CHARLES"; nft_name = "Sir Charles The 3rd"; nft_description = "A Bitcoin Ordinal Collection powered by a C++ LLM running on the Internet Computer"})' + // '()' -> '(variant { Ok = record { nft_supply_cap = 2 : nat64; nft_total_supply = 0 : nat64; nft_symbol = "CHARLES"; nft_name = "Sir Charles The 3rd"; nft_description = "A Bitcoin Ordinal Collection powered by a C++ LLM running on the Internet Computer"}})' mockIC.run_test( "nft_metadata", nft_metadata, "4449444c0000", - "4449444c016c05a5d0bfc60278ee86c3950871bb9effa20f71b9e4b5ac0f71ad91e7ca0f78010002000000000000001353697220436861726c6573205468652033726407434841524c4553524120426974636f696e204f7264696e616c20436f6c6c656374696f6e20706f7765726564206279206120432b2b204c4c4d2072756e6e696e67206f6e2074686520496e7465726e657420436f6d70757465720000000000000000", + "4449444c026c05a5d0bfc60278ee86c3950871bb9effa20f71b9e4b5ac0f71ad91e7ca0f786b01bc8a010001010002000000000000001353697220436861726c6573205468652033726407434841524c4553524120426974636f696e204f7264696e616c20436f6c6c656374696f6e20706f7765726564206279206120432b2b204c4c4d2072756e6e696e67206f6e2074686520496e7465726e657420436f6d70757465720000000000000000", silent_on_trap, anonymous_principal); // ------------------------------------------------------------------------ @@ -742,80 +767,113 @@ int main() { // Verify it traps when not owner of the canister // A regular user cannot mint NFTs - // '(record {token_id = "token-A" : text})' -> trap - mockIC.run_trap_test("nft_mint trap test", nft_mint, - "4449444c016c01a1a1c1da0271010007746f6b656e2d41", - silent_on_trap, your_principal); + // '(record {token_id = "token-A" : text})' + // -> '(variant { Err = record { Other = "Access Denied"} })' + mockIC.run_test( + "nft_mint Err test 1", nft_mint, + "4449444c016c01a1a1c1da0271010007746f6b656e2d41", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100000d4163636573732044656e696564", + silent_on_trap, your_principal); // A whitelisted principal can NOT mint NFTs, they can only create new stories - mockIC.run_trap_test("nft_mint trap test", nft_mint, - "4449444c016c01a1a1c1da0271010007746f6b656e2d41", - silent_on_trap, nft_whitelist_principal_id_user); + mockIC.run_test( + "nft_mint Err test 1", nft_mint, + "4449444c016c01a1a1c1da0271010007746f6b656e2d41", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100000d4163636573732044656e696564", + silent_on_trap, nft_whitelist_principal_id_user); // ----------- // Mint one // '(record {token_id = "token-A" : text})' - // -> '(variant { Ok = 200 : nat16 })' - mockIC.run_test( - "nft_mint", nft_mint, "4449444c016c01a1a1c1da0271010007746f6b656e2d41", - "4449444c016b01bc8a017a010000c800", silent_on_trap, my_principal); + // -> '(variant { Ok = record { status_code = 200 : nat16} })' + mockIC.run_test("nft_mint", nft_mint, + "4449444c016c01a1a1c1da0271010007746f6b656e2d41", + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // Summarize NFT collection metadata - // '()' -> '(record { nft_supply_cap = 2 : nat64; nft_total_supply = 1 : nat64; nft_symbol = "CHARLES"; nft_name = "Sir Charles The 3rd"; nft_description = "A Bitcoin Ordinal Collection powered by a C++ LLM running on the Internet Computer"})' + // '()' -> '(variant {Ok = record { nft_supply_cap = 2 : nat64; nft_total_supply = 1 : nat64; nft_symbol = "CHARLES"; nft_name = "Sir Charles The 3rd"; nft_description = "A Bitcoin Ordinal Collection powered by a C++ LLM running on the Internet Computer"}})' mockIC.run_test( "nft_metadata - total_supply = 1", nft_metadata, "4449444c0000", - "4449444c016c05a5d0bfc60278ee86c3950871bb9effa20f71b9e4b5ac0f71ad91e7ca0f78010002000000000000001353697220436861726c6573205468652033726407434841524c4553524120426974636f696e204f7264696e616c20436f6c6c656374696f6e20706f7765726564206279206120432b2b204c4c4d2072756e6e696e67206f6e2074686520496e7465726e657420436f6d70757465720100000000000000", + "4449444c026c05a5d0bfc60278ee86c3950871bb9effa20f71b9e4b5ac0f71ad91e7ca0f786b01bc8a010001010002000000000000001353697220436861726c6573205468652033726407434841524c4553524120426974636f696e204f7264696e616c20436f6c6c656374696f6e20706f7765726564206279206120432b2b204c4c4d2072756e6e696e67206f6e2074686520496e7465726e657420436f6d70757465720100000000000000", silent_on_trap, anonymous_principal); // ----------- - // Minting it again for the same ordinal must trap. + // Minting it again for the same ordinal must Err. // '(record {token_id = "token-A" : text})' - // -> TRAP - mockIC.run_trap_test("nft_mint only one per ordinal", nft_mint, - "4449444c016c01a1a1c1da0271010007746f6b656e2d41", - silent_on_trap, my_principal); + // -> '(variant { Err = record { Other = "An NFT for the token_id token-A already exists."} })' + mockIC.run_test( + "nft_mint only one per ordinal", nft_mint, + "4449444c016c01a1a1c1da0271010007746f6b656e2d41", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100002f416e204e465420666f722074686520746f6b656e5f696420746f6b656e2d4120616c7265616479206578697374732e", + silent_on_trap, my_principal); // ----------- // Mint another NFT with different token_id // '(record {token_id = "token-B" : text})' - // -> '(variant { Ok = 200 : nat16 })' + // -> '(variant { Ok = record { status_code = 200 : nat16} })' mockIC.run_test("nft_mint again", nft_mint, "4449444c016c01a1a1c1da0271010007746f6b656e2d42", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // Summarize NFT collection metadata - // '()' -> '(record { nft_supply_cap = 2 : nat64; nft_total_supply = 2 : nat64; nft_symbol = "CHARLES"; nft_name = "Sir Charles The 3rd"; nft_description = "A Bitcoin Ordinal Collection powered by a C++ LLM running on the Internet Computer"})' + // '()' -> '(variant = {Ok = record { nft_supply_cap = 2 : nat64; nft_total_supply = 2 : nat64; nft_symbol = "CHARLES"; nft_name = "Sir Charles The 3rd"; nft_description = "A Bitcoin Ordinal Collection powered by a C++ LLM running on the Internet Computer"}})' mockIC.run_test( "nft_metadata - total_supply = 2", nft_metadata, "4449444c0000", - "4449444c016c05a5d0bfc60278ee86c3950871bb9effa20f71b9e4b5ac0f71ad91e7ca0f78010002000000000000001353697220436861726c6573205468652033726407434841524c4553524120426974636f696e204f7264696e616c20436f6c6c656374696f6e20706f7765726564206279206120432b2b204c4c4d2072756e6e696e67206f6e2074686520496e7465726e657420436f6d70757465720200000000000000", + "4449444c026c05a5d0bfc60278ee86c3950871bb9effa20f71b9e4b5ac0f71ad91e7ca0f786b01bc8a010001010002000000000000001353697220436861726c6573205468652033726407434841524c4553524120426974636f696e204f7264696e616c20436f6c6c656374696f6e20706f7765726564206279206120432b2b204c4c4d2072756e6e696e67206f6e2074686520496e7465726e657420436f6d70757465720200000000000000", silent_on_trap, anonymous_principal); // ----------- - // Minting another NFT for another ordinal must trap, because we reached supply_cap + // Minting another NFT for another ordinal must Err, because we reached supply_cap // '(record {token_id = "token-C" : text})' - // -> TRAP - mockIC.run_trap_test("nft_mint hit supply_cap", nft_mint, - "4449444c016c01a1a1c1da0271010007746f6b656e2d43", - silent_on_trap, my_principal); + // -> '(variant { Err = record { Other = "Cannot mint, reached supply_cap of 2"} })' + mockIC.run_test( + "nft_mint hit supply_cap", nft_mint, + "4449444c016c01a1a1c1da0271010007746f6b656e2d43", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100002443616e6e6f74206d696e742c207265616368656420737570706c795f636170206f662032", + silent_on_trap, my_principal); // ------------------------------------------------------------------------ + // calling nft_get_story on non-existing token_id must fail + + // '(record {token_id = "token-ERR" : text})' + // -> '(variant { Err = variant { Other = "NFT token-ERR does not exists." : text} })' + mockIC.run_test( + "nft_get_story Err 0", nft_get_story, + "4449444c016c01a1a1c1da0271010009746f6b656e2d455252", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100001e4e465420746f6b656e2d45525220646f6573206e6f74206578697374732e", + silent_on_trap, my_principal); + + // ------------------------------------------------------------------------ + // calling nft_get_story on existing token_id without a story must fail + + // '(record {token_id = "token-A" : text})' + // -> '(variant { Err = variant { Other = "The story for NFT token-A does not exists." : text} })' + mockIC.run_test( + "nft_get_story Err 1", nft_get_story, + "4449444c016c01a1a1c1da0271010007746f6b656e2d41", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100002a5468652073746f727920666f72204e465420746f6b656e2d4120646f6573206e6f74206578697374732e", + silent_on_trap, my_principal); + // ------------------------------------------------------------------------ // Create the story for nft_id=0, with token_id="token-A" // With temperature=0.0: greedy argmax sampling -> the story will be the same every time // '(record {token_id = "token-A" : text}, record{ prompt = "It was a bright sunny day and Charles went to the beach with his fishing pole." : text; steps = 100 : nat64; temperature = 0.0 : float32; topp = 1.0 : float32; rng_seed = 0 : nat64;})' expected_response = "-to-do-B-"; if (model_to_use == 1) { - // -> '(variant { Ok = "...some story..." : text })' + // -> '(variant { Ok = record { inference = "...some story..." : text;} })' expected_response = - "4449444c016b01bc8a01710100004e4974207761732061206272696768742073756e6e792064617920616e6420436861726c65732077656e7420746f207468652062656163682077697468206869732066697368696e6720706f6c652e"; + "4449444c026c01d9b3b9980f716b01bc8a01000101004e4974207761732061206272696768742073756e6e792064617920616e6420436861726c65732077656e7420746f207468652062656163682077697468206869732066697368696e6720706f6c652e"; } else if (model_to_use == 2) { } else if (model_to_use == 3) { } else if (model_to_use == 4) { } - // Verify it traps when caller is not whitelisted + // Verify it returns Err when caller is not whitelisted // A regular user cannot create new stories for NFTs - mockIC.run_trap_test( - "nft_story_start trap test", nft_story_start, + // -> '(variant { Err = variant { Other = "Access Denied - You are not authorized to call this function." } })' + mockIC.run_test( + "nft_story_start Err test", nft_story_start, "4449444c026c01a1a1c1da02716c05b4e8c2e40373bbb885e80473a7f7b9a00878c5c8cea60878a4a3e1aa0b7102000107746f6b656e2d41000000000000803f640000000000000000000000000000004e4974207761732061206272696768742073756e6e792064617920616e6420436861726c65732077656e7420746f207468652062656163682077697468206869732066697368696e6720706f6c652e", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100003d4163636573732044656e696564202d20596f7520617265206e6f7420617574686f72697a656420746f2063616c6c20746869732066756e6374696f6e2e", silent_on_trap, your_principal); mockIC.run_test( "nft_story_start 0", nft_story_start, @@ -826,18 +884,20 @@ int main() { // '(record {token_id = "token-A" : text}, record{ prompt = "" : text; steps = 100 : nat64; temperature = 0.0 : float32; topp = 1.0 : float32; rng_seed = 0 : nat64;})' expected_response = "-to-do-B-"; if (model_to_use == 1) { - // -> '(variant { Ok = "...some story..." : text })' + // -> '(variant { Ok = record { inference = "...some story..." : text;} })' expected_response = - "4449444c016b01bc8a0171010000a701204865207761732076657279206578636974656420746f2073656520776861742077617320696e736964652e204865207761732076657279206578636974656420746f2073656520776861742077617320696e736964652e0a2248656c6c6f2c20436861726c69652122207361696420436861726c69652e0a2249276d20736f7272792c22207361696420436861726c69652e0a2249276d20736f7272792c2220736169642043"; + "4449444c026c01d9b3b9980f716b01bc8a0100010100a701204865207761732076657279206578636974656420746f2073656520776861742077617320696e736964652e204865207761732076657279206578636974656420746f2073656520776861742077617320696e736964652e0a2248656c6c6f2c20436861726c69652122207361696420436861726c69652e0a2249276d20736f7272792c22207361696420436861726c69652e0a2249276d20736f7272792c2220736169642043"; } else if (model_to_use == 2) { } else if (model_to_use == 3) { } else if (model_to_use == 4) { } - // Verify it traps when caller is not whitelisted + // Verify it returns Err when caller is not whitelisted // A regular user cannot continue stories for NFTs - mockIC.run_trap_test( - "nft_story_continue trap test", nft_story_continue, + // -> '(variant { Err = variant { Other = "Access Denied - You are not authorized to call this function." } })' + mockIC.run_test( + "nft_story_continue Err test", nft_story_continue, "4449444c026c01a1a1c1da02716c05b4e8c2e40373bbb885e80473a7f7b9a00878c5c8cea60878a4a3e1aa0b7102000107746f6b656e2d41000000000000803f6400000000000000000000000000000000", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100003d4163636573732044656e696564202d20596f7520617265206e6f7420617574686f72697a656420746f2063616c6c20746869732066756e6374696f6e2e", silent_on_trap, your_principal); mockIC.run_test( "nft_story_continue 0", nft_story_continue, @@ -851,18 +911,20 @@ int main() { // '(record {token_id = "token-B" : text}, record{ prompt = "Charles had a boat." : text; steps = 100 : nat64; temperature = 0.0 : float32; topp = 1.0 : float32; rng_seed = 0 : nat64;})' expected_response = "-to-do-B-"; if (model_to_use == 1) { - // -> '(variant { Ok = "...some story..." : text })' + // -> '(variant { Ok = record { inference = "...some story..." : text;} })' expected_response = - "4449444c016b01bc8a017101000013436861726c657320686164206120626f61742e"; + "4449444c026c01d9b3b9980f716b01bc8a010001010013436861726c657320686164206120626f61742e"; } else if (model_to_use == 2) { } else if (model_to_use == 3) { } else if (model_to_use == 4) { } - // Verify it traps when caller is not whitelisted + // Verify it returns Err when caller is not whitelisted // A regular user cannot create new stories for NFTs - mockIC.run_trap_test( - "nft_story_start 1 trap test", nft_story_start, + // -> '(variant { Err = variant { Other = "Access Denied - You are not authorized to call this function." } })' + mockIC.run_test( + "nft_story_start 1 Err test", nft_story_start, "4449444c026c01a1a1c1da02716c05b4e8c2e40373bbb885e80473a7f7b9a00878c5c8cea60878a4a3e1aa0b7102000107746f6b656e2d42000000000000803f6400000000000000000000000000000013436861726c657320686164206120626f61742e", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100003d4163636573732044656e696564202d20596f7520617265206e6f7420617574686f72697a656420746f2063616c6c20746869732066756e6374696f6e2e", silent_on_trap, your_principal); mockIC.run_test( "nft_story_start 1", nft_story_start, @@ -873,18 +935,20 @@ int main() { // '(record {token_id = "token-B" : text}, record{ prompt = "" : text; steps = 100 : nat64; temperature = 0.0 : float32; topp = 1.0 : float32; rng_seed = 0 : nat64;})' expected_response = "-to-do-B-"; if (model_to_use == 1) { - // -> '(variant { Ok = "...some story..." : text })' + // -> '(variant { Ok = record { inference = "...some story..." : text;} })' expected_response = - "4449444c016b01bc8a01710100008902204865206c696b656420746f20706c617920776974682068697320746f797320616e642072756e2061726f756e642074686520726f6f6d2e2048652077617320766572792068617070792e2048652077616e74656420746f20706c617920776974682068697320746f79732e0a4f6e65206461792c20436861726c69652073617720612062696720626f61742e2054686520626f6174207761732076657279207363617265642e2048652077616e74656420746f20706c617920776974682074686520626f61742e2048652077616e74656420746f20706c617920776974682074686520626f61742e2048652077616e74656420746f20706c617920776974682074686520626f6174"; + "4449444c026c01d9b3b9980f716b01bc8a01000101008902204865206c696b656420746f20706c617920776974682068697320746f797320616e642072756e2061726f756e642074686520726f6f6d2e2048652077617320766572792068617070792e2048652077616e74656420746f20706c617920776974682068697320746f79732e0a4f6e65206461792c20436861726c69652073617720612062696720626f61742e2054686520626f6174207761732076657279207363617265642e2048652077616e74656420746f20706c617920776974682074686520626f61742e2048652077616e74656420746f20706c617920776974682074686520626f61742e2048652077616e74656420746f20706c617920776974682074686520626f6174"; } else if (model_to_use == 2) { } else if (model_to_use == 3) { } else if (model_to_use == 4) { } - // Verify it traps when caller is not whitelisted - // A regular user cannot create new stories for NFTs - mockIC.run_trap_test( - "nft_story_continue trap test", nft_story_continue, + // Verify it returns Err when caller is not whitelisted + // A regular user cannot continue stories for NFTs + // -> '(variant { Err = variant { Other = "Access Denied - You are not authorized to call this function." } })' + mockIC.run_test( + "nft_story_continue Err test", nft_story_continue, "4449444c026c01a1a1c1da02716c05b4e8c2e40373bbb885e80473a7f7b9a00878c5c8cea60878a4a3e1aa0b7102000107746f6b656e2d42000000000000803f6400000000000000000000000000000000", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100003d4163636573732044656e696564202d20596f7520617265206e6f7420617574686f72697a656420746f2063616c6c20746869732066756e6374696f6e2e", silent_on_trap, your_principal); mockIC.run_test( "nft_story_continue 1", nft_story_continue, @@ -895,12 +959,11 @@ int main() { // Get the story for nft_id=0, with token_id="token-A" // '(record {token_id = "token-A" : text})' - // -> '(variant { Ok = "...story..." : text })' + // -> '(variant { Ok = record { story = "...story..." : text;} })' expected_response = "-to-do-B-"; if (model_to_use == 1) { - // -> '(variant { Ok = "...some story..." : text })' expected_response = - "4449444c016b01bc8a0171010000f5014974207761732061206272696768742073756e6e792064617920616e6420436861726c65732077656e7420746f207468652062656163682077697468206869732066697368696e6720706f6c652e204865207761732076657279206578636974656420746f2073656520776861742077617320696e736964652e204865207761732076657279206578636974656420746f2073656520776861742077617320696e736964652e0a2248656c6c6f2c20436861726c69652122207361696420436861726c69652e0a2249276d20736f7272792c22207361696420436861726c69652e0a2249276d20736f7272792c2220736169642043"; + "4449444c026c01f5a7d8a008716b01bc8a0100010100f5014974207761732061206272696768742073756e6e792064617920616e6420436861726c65732077656e7420746f207468652062656163682077697468206869732066697368696e6720706f6c652e204865207761732076657279206578636974656420746f2073656520776861742077617320696e736964652e204865207761732076657279206578636974656420746f2073656520776861742077617320696e736964652e0a2248656c6c6f2c20436861726c69652122207361696420436861726c69652e0a2249276d20736f7272792c22207361696420436861726c69652e0a2249276d20736f7272792c2220736169642043"; } else if (model_to_use == 2) { } else if (model_to_use == 3) { } else if (model_to_use == 4) { @@ -913,12 +976,11 @@ int main() { // Get the story for nft_id=1, with token_id="token-B" // '(record {token_id = "token-B" : text})' - // -> '(variant { Ok = "...story..." : text })' + // -> '(variant { Ok = record { story = "...story..." : text;} })' expected_response = "-to-do-B-"; if (model_to_use == 1) { - // -> '(variant { Ok = "...some story..." : text })' expected_response = - "4449444c016b01bc8a01710100009c02436861726c657320686164206120626f61742e204865206c696b656420746f20706c617920776974682068697320746f797320616e642072756e2061726f756e642074686520726f6f6d2e2048652077617320766572792068617070792e2048652077616e74656420746f20706c617920776974682068697320746f79732e0a4f6e65206461792c20436861726c69652073617720612062696720626f61742e2054686520626f6174207761732076657279207363617265642e2048652077616e74656420746f20706c617920776974682074686520626f61742e2048652077616e74656420746f20706c617920776974682074686520626f61742e2048652077616e74656420746f20706c617920776974682074686520626f6174"; + "4449444c026c01f5a7d8a008716b01bc8a01000101009c02436861726c657320686164206120626f61742e204865206c696b656420746f20706c617920776974682068697320746f797320616e642072756e2061726f756e642074686520726f6f6d2e2048652077617320766572792068617070792e2048652077616e74656420746f20706c617920776974682068697320746f79732e0a4f6e65206461792c20436861726c69652073617720612062696720626f61742e2054686520626f6174207761732076657279207363617265642e2048652077616e74656420746f20706c617920776974682074686520626f61742e2048652077616e74656420746f20706c617920776974682074686520626f61742e2048652077616e74656420746f20706c617920776974682074686520626f6174"; } else if (model_to_use == 2) { } else if (model_to_use == 3) { } else if (model_to_use == 4) { @@ -1015,59 +1077,50 @@ int main() { // ----------------------------------------------------------------------------------------- // Users data - // Verify that calls trap when not owner - mockIC.run_trap_test("get_user_count", get_user_count, "4449444c0000", - silent_on_trap, your_principal); - - mockIC.run_trap_test("get_user_ids", get_user_ids, "4449444c0000", - silent_on_trap, your_principal); - - mockIC.run_trap_test("get_user_metadata", get_user_metadata, - "4449444c000171093269626f372d646961", silent_on_trap, - your_principal); + // Verify that calls Err when not owner + // (variant { Err = variant { Other = "Access Denied" } }) + mockIC.run_test( + "get_users Err test", get_users, "4449444c0000", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100000d4163636573732044656e696564", + silent_on_trap, your_principal); + mockIC.run_test( + "get_user_metadata Err test", get_user_metadata, + "4449444c000171093269626f372d646961", + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100000d4163636573732044656e696564", + silent_on_trap, your_principal); // '()' -> '(4 : nat64)' - mockIC.run_test("get_user_count", get_user_count, "4449444c0000", - "4449444c0001780400000000000000", silent_on_trap, - my_principal); - /* - '()' -> - ( - vec { - "token-B"; - "token-A"; - "2ibo7-dia"; - "expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae"; - }, - ) + '()' + -> '( variant { + Ok = record { + user_count = 4 : nat64; + user_ids = vec { + "token-B"; + "token-A"; + "2ibo7-dia"; + "expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae"; + }; + } + }, + )' */ mockIC.run_test( - "get_user_ids", get_user_ids, "4449444c0000", - "4449444c016d7101000407746f6b656e2d4207746f6b656e2d41093269626f372d6469613f6578706d742d67747873772d696e66746a2d747461626a2d71687035732d6e6f7a75702d6e3362626f2d6b377a766e2d64673468652d6b6e6163332d6c6165", + "get_users", get_users, "4449444c0000", + "4449444c036c029bd1ed017884ba9db801016d716b01bc8a010001020004000000000000000407746f6b656e2d4207746f6b656e2d41093269626f372d6469613f6578706d742d67747873772d696e66746a2d747461626a2d71687035732d6e6f7a75702d6e3362626f2d6b377a766e2d64673468652d6b6e6163332d6c6165", silent_on_trap, my_principal); - // '("2ibo7-dia")' -> ... two vectors of nat64 ... but we can not check time-stamp! - /* - ( - vec { - 1_695_424_351_661_183_242 : nat64; - 1_695_424_351_661_875_047 : nat64; - 1_695_424_351_885_991_917 : nat64; - }, - vec { 0 : nat64; 620 : nat64; 620 : nat64 }, - ) - */ + // '("2ibo7-dia")' -> a UserMetadataRecord... two vectors of nat64 ... but we can not check time-stamp! mockIC.run_test("get_user_metadata", get_user_metadata, "4449444c000171093269626f372d646961", "", silent_on_trap, my_principal); // ----------------------------------------------------------------------------------------- // Reset the model - // '()' -> '(variant { Ok = 200 : nat16 })' + // '()' -> '(variant { Ok = record { status_code = 200 : nat16} })' mockIC.run_test("reset_model", reset_model, "4449444c0000", - "4449444c016b01bc8a017a010000c800", silent_on_trap, - my_principal); + "4449444c026c019aa1b2f90c7a6b01bc8a0100010100c800", + silent_on_trap, my_principal); // ----------------------------------------------------------------------------- // returns 1 if any tests failed diff --git a/icpp_llama2/scripts/ic_py_canister.py b/icpp_llama2/scripts/ic_py_canister.py index ffcd160..efbf06b 100644 --- a/icpp_llama2/scripts/ic_py_canister.py +++ b/icpp_llama2/scripts/ic_py_canister.py @@ -1,4 +1,5 @@ """Returns the ic-py Canister instance, for calling the endpoints.""" + import sys import platform import subprocess @@ -36,7 +37,10 @@ def run_dfx_command(cmd: str) -> Optional[str]: def get_canister( - canister_name: str, candid_path: Path, network: str = "local" + canister_name: str, + candid_path: Path, + network: str = "local", + canister_id: Optional[str] = "", ) -> Canister: """Returns an ic_py Canister instance""" @@ -67,10 +71,13 @@ def get_canister( identity_whoami = run_dfx_command(f"{DFX} identity whoami ") print(f"Using identity = {identity_whoami}") - # Get the id of the canister - canister_id = run_dfx_command( - f"{DFX} canister --network {network} id {canister_name} " - ) + # Try to get the id of the canister if not provided explicitly + # This only works from the same directory as where you deployed from. + # So we also provide the option to just pass in the canister_id directly + if canister_id == "": + canister_id = run_dfx_command( + f"{DFX} canister --network {network} id {canister_name} " + ) print(f"Canister ID = {canister_id}") # Get the private key of the current identity diff --git a/icpp_llama2/scripts/nft_init.py b/icpp_llama2/scripts/nft_init.py index 4cde272..e566876 100644 --- a/icpp_llama2/scripts/nft_init.py +++ b/icpp_llama2/scripts/nft_init.py @@ -28,6 +28,7 @@ def main() -> int: network = args.network canister_name = args.canister + canister_id = args.canister_id candid_path = ROOT_PATH / args.candid nft_supply_cap = args.nft_supply_cap @@ -41,6 +42,7 @@ def main() -> int: f"Summary of canister & NFT Collection:" f"\n - network = {network}" f"\n - canister = {canister_name}" + f"\n - canister_id = {canister_id}" f"\n - dfx_json_path = {dfx_json_path}" f"\n - candid_path = {candid_path}" f"\n - nft_supply_cap = {nft_supply_cap}" @@ -51,7 +53,7 @@ def main() -> int: # --------------------------------------------------------------------------- # get ic-py based Canister instance - canister_llama2 = get_canister(canister_name, candid_path, network) + canister_llama2 = get_canister(canister_name, candid_path, network, canister_id) # check health (liveness) print("--\nChecking liveness of canister (did we deploy it!)") diff --git a/icpp_llama2/scripts/nft_metadata.py b/icpp_llama2/scripts/nft_metadata.py index e8d95e8..b78d622 100644 --- a/icpp_llama2/scripts/nft_metadata.py +++ b/icpp_llama2/scripts/nft_metadata.py @@ -28,6 +28,7 @@ def main() -> int: network = args.network canister_name = args.canister + canister_id = args.canister_id candid_path = ROOT_PATH / args.candid dfx_json_path = ROOT_PATH / "dfx.json" @@ -36,13 +37,14 @@ def main() -> int: f"Summary of canister & NFT Collection:" f"\n - network = {network}" f"\n - canister = {canister_name}" + f"\n - canister_id = {canister_id}" f"\n - dfx_json_path = {dfx_json_path}" f"\n - candid_path = {candid_path}" ) # --------------------------------------------------------------------------- # get ic-py based Canister instance - canister_llama2 = get_canister(canister_name, candid_path, network) + canister_llama2 = get_canister(canister_name, candid_path, network, canister_id) # check health (liveness) print("--\nChecking liveness of canister (did we deploy it!)") diff --git a/icpp_llama2/scripts/nft_mint.py b/icpp_llama2/scripts/nft_mint.py index aad562d..c0a1cff 100644 --- a/icpp_llama2/scripts/nft_mint.py +++ b/icpp_llama2/scripts/nft_mint.py @@ -30,6 +30,7 @@ def main() -> int: network = args.network canister_name = args.canister + canister_id = args.canister_id candid_path = ROOT_PATH / args.candid nft_config_path = Path(args.nft_config) @@ -41,6 +42,7 @@ def main() -> int: f"Summary of canister & NFT Collection:" f"\n - network = {network}" f"\n - canister = {canister_name}" + f"\n - canister_id = {canister_id}" f"\n - dfx_json_path = {dfx_json_path}" f"\n - candid_path = {candid_path}" f"\n - nft_config_path = {nft_config_path}" @@ -52,7 +54,7 @@ def main() -> int: # --------------------------------------------------------------------------- # get ic-py based Canister instance - canister_llama2 = get_canister(canister_name, candid_path, network) + canister_llama2 = get_canister(canister_name, candid_path, network, canister_id) # check health (liveness) print("--\nChecking liveness of canister (did we deploy it!)") diff --git a/icpp_llama2/scripts/nft_update_story.py b/icpp_llama2/scripts/nft_update_story.py index 335aed9..703ea2d 100644 --- a/icpp_llama2/scripts/nft_update_story.py +++ b/icpp_llama2/scripts/nft_update_story.py @@ -30,6 +30,7 @@ def main() -> int: network = args.network canister_name = args.canister + canister_id = args.canister_id candid_path = ROOT_PATH / args.candid nft_config_path = Path(args.nft_config) @@ -41,6 +42,7 @@ def main() -> int: f"Summary of canister & NFT Collection:" f"\n - network = {network}" f"\n - canister = {canister_name}" + f"\n - canister_id = {canister_id}" f"\n - dfx_json_path = {dfx_json_path}" f"\n - candid_path = {candid_path}" f"\n - nft_config_path = {nft_config_path}" @@ -52,7 +54,7 @@ def main() -> int: # --------------------------------------------------------------------------- # get ic-py based Canister instance - canister_llama2 = get_canister(canister_name, candid_path, network) + canister_llama2 = get_canister(canister_name, candid_path, network, canister_id) # check health (liveness) print("--\nChecking liveness of canister (did we deploy it!)") @@ -154,7 +156,7 @@ def main() -> int: print(response) if "Ok" in response[0].keys(): # Check if the response is an empty string. If it is, break out of the loop. - if response[0]["Ok"] == "": + if response[0]["Ok"]["inference"] == "": print("The end!") break else: diff --git a/icpp_llama2/scripts/parse_args_nft_init.py b/icpp_llama2/scripts/parse_args_nft_init.py index 136c58b..0d0a041 100644 --- a/icpp_llama2/scripts/parse_args_nft_init.py +++ b/icpp_llama2/scripts/parse_args_nft_init.py @@ -18,6 +18,12 @@ def parse_args() -> argparse.Namespace: default="no-default", help="canister name in dfx.json", ) + parser.add_argument( + "--canister-id", + type=str, + default="", + help="canister-id name canister_ids.json", + ) parser.add_argument( "--candid", type=str, diff --git a/icpp_llama2/scripts/parse_args_nft_metadata.py b/icpp_llama2/scripts/parse_args_nft_metadata.py index 9859bcd..4683b38 100644 --- a/icpp_llama2/scripts/parse_args_nft_metadata.py +++ b/icpp_llama2/scripts/parse_args_nft_metadata.py @@ -18,6 +18,12 @@ def parse_args() -> argparse.Namespace: default="no-default", help="canister name in dfx.json", ) + parser.add_argument( + "--canister-id", + type=str, + default="", + help="canister-id name canister_ids.json", + ) parser.add_argument( "--candid", type=str, diff --git a/icpp_llama2/scripts/parse_args_nft_mint.py b/icpp_llama2/scripts/parse_args_nft_mint.py index 0b2cb75..237abed 100644 --- a/icpp_llama2/scripts/parse_args_nft_mint.py +++ b/icpp_llama2/scripts/parse_args_nft_mint.py @@ -20,6 +20,12 @@ def parse_args() -> argparse.Namespace: default="no-default", help="canister name in dfx.json", ) + parser.add_argument( + "--canister-id", + type=str, + default="", + help="canister-id name canister_ids.json", + ) parser.add_argument( "--candid", type=str, diff --git a/icpp_llama2/scripts/parse_args_upload.py b/icpp_llama2/scripts/parse_args_upload.py index d506312..fc34aa8 100644 --- a/icpp_llama2/scripts/parse_args_upload.py +++ b/icpp_llama2/scripts/parse_args_upload.py @@ -18,6 +18,12 @@ def parse_args() -> argparse.Namespace: default="no-default", help="canister name in dfx.json", ) + parser.add_argument( + "--canister-id", + type=str, + default="", + help="canister-id name canister_ids.json", + ) parser.add_argument( "--candid", type=str, diff --git a/icpp_llama2/scripts/upload.py b/icpp_llama2/scripts/upload.py index 5055593..f9946e3 100644 --- a/icpp_llama2/scripts/upload.py +++ b/icpp_llama2/scripts/upload.py @@ -49,6 +49,7 @@ def main() -> int: network = args.network canister_name = args.canister + canister_id = args.canister_id candid_path = ROOT_PATH / args.candid model_path = ROOT_PATH / args.model tokenizer_path = ROOT_PATH / args.tokenizer @@ -60,6 +61,7 @@ def main() -> int: f"Summary of model & NFT Collection:" f"\n - network = {network}" f"\n - canister = {canister_name}" + f"\n - canister_id = {canister_id}" f"\n - dfx_json_path = {dfx_json_path}" f"\n - candid_path = {candid_path}" f"\n - model_path = {model_path}" @@ -68,7 +70,7 @@ def main() -> int: # --------------------------------------------------------------------------- # get ic-py based Canister instance - canister_llama2 = get_canister(canister_name, candid_path, network) + canister_llama2 = get_canister(canister_name, candid_path, network, canister_id) # check health (liveness) print("--\nChecking liveness of canister (did we deploy it!)") diff --git a/icpp_llama2/src/canister.cpp b/icpp_llama2/src/canister.cpp index df17d56..d48117f 100644 --- a/icpp_llama2/src/canister.cpp +++ b/icpp_llama2/src/canister.cpp @@ -20,6 +20,7 @@ void set_canister_owner(IC_API &ic_api) { if (p_canister_owner_principal == nullptr) { p_canister_owner_principal = new (std::nothrow) std::string(); if (p_canister_owner_principal == nullptr) { + // called from canister_init, so trap is correct! IC_API::trap("Allocation of p_canister_owner_principal failed"); } } @@ -38,8 +39,9 @@ bool is_canister_owner(IC_API &ic_api, bool err_to_wire) { ": ERROR - caller is not the owner."); if (err_to_wire) { - uint16_t status_code = Http::StatusCode::Unauthorized; - ic_api.to_wire(CandidTypeVariant{"Err", CandidTypeNat16{status_code}}); + std::string error_msg = "Access Denied"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); } return false; } @@ -62,20 +64,31 @@ void set_canister_mode() { IC_API ic_api(CanisterUpdate{std::string(__func__)}, false); // Only the canister owner is allowed to set the canister mode - if (!is_canister_owner(ic_api, false)) IC_API::trap("Access Denied"); + if (!is_canister_owner(ic_api, false)) { + std::string error_msg = "Access Denied"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } // The canister mode is passed by argument std::string canister_mode; ic_api.from_wire(CandidTypeText(&canister_mode)); if (!is_canister_mode_valid(canister_mode)) { - IC_API::trap("ERROR: unknown canister_mode = " + canister_mode); + std::string error_msg = "ERROR: unknown canister_mode = " + canister_mode; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; } if (p_canister_mode == nullptr) { p_canister_mode = new (std::nothrow) std::string(); if (p_canister_mode == nullptr) { - IC_API::trap("Allocation of p_canister_mode failed"); + std::string error_msg = "Allocation of p_canister_mode failed"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; } } *p_canister_mode = canister_mode; @@ -83,7 +96,10 @@ void set_canister_mode() { IC_API::debug_print(std::string(__func__) + ": p_canister_mode = " + *p_canister_mode); - ic_api.to_wire(); + CandidTypeRecord canister_mode_record; + canister_mode_record.append("canister_mode", + CandidTypeText{*p_canister_mode}); + ic_api.to_wire(CandidTypeVariant{"Ok", canister_mode_record}); } void print_canister_metadata() { diff --git a/icpp_llama2/src/chats.cpp b/icpp_llama2/src/chats.cpp index d7d2de5..81fecb8 100644 --- a/icpp_llama2/src/chats.cpp +++ b/icpp_llama2/src/chats.cpp @@ -16,6 +16,7 @@ void new_p_chats() { IC_API::debug_print(std::string(__func__) + ": Creating p_chats instance."); p_chats = new (std::nothrow) Chats(); if (p_chats == nullptr) { + // called from canister_init, so trap is correct! IC_API::trap("Allocation of p_chats failed"); } } @@ -25,6 +26,7 @@ void new_p_chats() { ": Creating p_chats_output_history instance."); p_chats_output_history = new (std::nothrow) ChatsOutputHistory(); if (p_chats_output_history == nullptr) { + // called from canister_init, so trap is correct! IC_API::trap("Allocation of p_chats_output_history failed"); } } @@ -55,6 +57,7 @@ void new_p_metadata_users() { ": Creating p_metadata_users instance."); p_metadata_users = new (std::nothrow) MetadataUsers(); if (p_metadata_users == nullptr) { + // called from canister_init, so trap is correct! IC_API::trap("Allocation of p_metadata_users failed"); } } @@ -84,7 +87,7 @@ void init_run_state(RunState *s) { } // key = principal or ordinal-id -void build_new_chat(std::string key) { +bool build_new_chat(std::string key, IC_API &ic_api) { // -------------------------------------------------------------------------- // Create entries for this key in the persisted memory, if not yet done if (p_chats && p_chats->umap.find(key) == p_chats->umap.end()) { @@ -112,7 +115,12 @@ void build_new_chat(std::string key) { // Reset the Chat data Chat *chat = &p_chats->umap[key]; free_run_state(&chat->state); - malloc_run_state(&chat->state, &transformer.config); + if (!malloc_run_state(&chat->state, &transformer.config)) { + std::string error_msg = "malloc_run_state failed"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return false; + } // Reset the output data std::string *output_history = &p_chats_output_history->umap[key]; @@ -135,9 +143,11 @@ void build_new_chat(std::string key) { MetadataChat metadata_chat; metadata_chat.start_time = IC_API::time(); // time in ns metadata_user->metadata_chats.push_back(metadata_chat); + + return true; } -bool is_ready_and_authorized(IC_API ic_api) { +bool is_ready_and_authorized(IC_API &ic_api) { if (!ready_for_inference) { uint16_t status_code = Http::StatusCode::InternalServerError; @@ -161,14 +171,22 @@ bool is_ready_and_authorized(IC_API ic_api) { // Endpoint to be called by user when starting a new chat void new_chat() { IC_API ic_api(CanisterUpdate{std::string(__func__)}, false); - if (!is_canister_mode_chat_principal()) IC_API::trap("Access Denied"); + if (!is_canister_mode_chat_principal()) { + std::string error_msg = + "Access Denied: canister_mode is not set to 'principal'."; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } if (!is_ready_and_authorized(ic_api)) return; CandidTypePrincipal caller = ic_api.get_caller(); std::string principal = caller.get_text(); - build_new_chat(principal); + if (!build_new_chat(principal, ic_api)) return; - ic_api.to_wire( - CandidTypeVariant{"Ok", CandidTypeNat16{Http::StatusCode::OK}}); + CandidTypeRecord status_code_record; + status_code_record.append("status_code", + CandidTypeNat16{Http::StatusCode::OK}); + ic_api.to_wire(CandidTypeVariant{"Ok", status_code_record}); } \ No newline at end of file diff --git a/icpp_llama2/src/chats.h b/icpp_llama2/src/chats.h index ed6540c..6be8fb3 100644 --- a/icpp_llama2/src/chats.h +++ b/icpp_llama2/src/chats.h @@ -57,5 +57,5 @@ void new_p_chats(); void delete_p_chats(); void new_p_metadata_users(); void delete_p_metadata_users(); -void build_new_chat(std::string key); -bool is_ready_and_authorized(IC_API ic_api); \ No newline at end of file +bool build_new_chat(std::string key, IC_API &ic_api); +bool is_ready_and_authorized(IC_API &ic_api); \ No newline at end of file diff --git a/icpp_llama2/src/inference.cpp b/icpp_llama2/src/inference.cpp index 74af0da..5c82901 100644 --- a/icpp_llama2/src/inference.cpp +++ b/icpp_llama2/src/inference.cpp @@ -44,7 +44,12 @@ std::string safe_stringify(const char *piece) { // Copied from run.c and modified slightly std::string generate(IC_API ic_api, Chat *chat, Transformer *transformer, Tokenizer *tokenizer, Sampler *sampler, std::string prompt, - int steps) { + int steps, bool *error) { + // --- DEBUG TEST + // *error = true; + // return "Testing return of error=true from 'generate'."; + //--- DEBUG TEST END + *error = false; std::string output; // encode the (string) prompt into tokens sequence @@ -52,11 +57,27 @@ std::string generate(IC_API ic_api, Chat *chat, Transformer *transformer, // +3 for '\0', ?BOS, ?EOS int *prompt_tokens = (int *)malloc((prompt.length() + 3) * sizeof(int)); - if (!prompt_tokens) - IC_API::trap("Failed to allocate memory for prompt_tokens."); + if (!prompt_tokens) { + *error = true; + return "Failed to allocate memory for prompt_tokens."; + } // We do not pass bos, but next, which is 1 after new_chat, else last token of previous call + int error_code = 0; encode(tokenizer, prompt.c_str(), chat->next, chat->eos, prompt_tokens, - &num_prompt_tokens); + &num_prompt_tokens, &error_code); + if (error_code != 0) { + std::string error_msg; + if (error_code == 1) { + error_msg = "cannot encode NULL text in 'encode' function of LLM."; + } else if (error_code == 2) { + error_msg = + "allocation failed of str_buffer in 'encode' function of LLM."; + } else { + error_msg = "Unknown error occured in 'encode' function of LLM."; + } + *error = true; + return error_msg; + } // icpp: make sure we do not re-add bos next time, unless reset by new_chat chat->bos = 0; @@ -144,7 +165,13 @@ std::string generate(IC_API ic_api, Chat *chat, Transformer *transformer, // Inference endpoint for ICGPT, with story ownership based on principal of caller void inference() { IC_API ic_api(CanisterUpdate{std::string(__func__)}, false); - if (!is_canister_mode_chat_principal()) IC_API::trap("Access Denied"); + if (!is_canister_mode_chat_principal()) { + std::string error_msg = + "Access Denied: canister_mode is not set to 'principal'."; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } if (!is_ready_and_authorized(ic_api)) return; // Get the Prompt from the wire @@ -162,22 +189,41 @@ void inference() { std::string principal = caller.get_text(); if (p_chats && p_chats->umap.find(principal) == p_chats->umap.end()) { - build_new_chat(principal); + if (!build_new_chat(principal, ic_api)) return; } if (!p_chats || !p_chats_output_history) { - IC_API::trap("ERROR: null pointers that should not be null in function " + - std::string(__func__)); + std::string error_msg = + "ERROR: null pointers that should not be null in function " + + std::string(__func__); + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; } Chat *chat = &p_chats->umap[principal]; std::string *output_history = &p_chats_output_history->umap[principal]; MetadataUser *metadata_user = &p_metadata_users->umap[principal]; - do_inference(ic_api, wire_prompt, chat, output_history, metadata_user); + bool error{false}; + std::string output = do_inference(ic_api, wire_prompt, chat, output_history, + metadata_user, &error); + + if (error) { + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{output}}}); + return; + } + + // IC_API::debug_print(output); + // Send the generated response to the wire + CandidTypeRecord inference_record; + inference_record.append("inference", CandidTypeText{output}); + ic_api.to_wire(CandidTypeVariant{"Ok", CandidTypeRecord{inference_record}}); } -void do_inference(IC_API &ic_api, Prompt wire_prompt, Chat *chat, - std::string *output_history, MetadataUser *metadata_user) { +std::string do_inference(IC_API &ic_api, Prompt wire_prompt, Chat *chat, + std::string *output_history, + MetadataUser *metadata_user, bool *error) { // parameter validation/overrides if (wire_prompt.rng_seed <= 0) @@ -209,28 +255,27 @@ void do_inference(IC_API &ic_api, Prompt wire_prompt, Chat *chat, std::string output; // if (mode == "generate") { output += generate(ic_api, chat, &transformer, &tokenizer, &sampler, - wire_prompt.prompt, wire_prompt.steps); + wire_prompt.prompt, wire_prompt.steps, error); // } else if (mode =="chat") { // chat(&transformer, &tokenizer, &sampler, prompt, system_prompt, steps); // } else { - // IC_API::trap("unsupported mode: " + mode); + // return an error about: "unsupported mode: " + mode) // } - // Update & persist full output using Orthogonal Persistence - *output_history += output; + if (!*error) { + // Update & persist full output using Orthogonal Persistence + *output_history += output; - // Now we have the total_steps, stored with the chat - // And we can update the metadata_user - if (!metadata_user->metadata_chats.empty()) { - MetadataChat &metadata_chat = metadata_user->metadata_chats.back(); - metadata_chat.total_steps += chat->total_steps; + // Now we have the total_steps, stored with the chat + // And we can update the metadata_user + if (!metadata_user->metadata_chats.empty()) { + MetadataChat &metadata_chat = metadata_user->metadata_chats.back(); + metadata_chat.total_steps += chat->total_steps; + } } // memory and file handles cleanup free_sampler(&sampler); - // IC_API::debug_print(output); - - // Send the generated response to the wire - ic_api.to_wire(CandidTypeVariant{"Ok", CandidTypeText{output}}); + return output; } diff --git a/icpp_llama2/src/inference.h b/icpp_llama2/src/inference.h index 01ab020..56f787a 100644 --- a/icpp_llama2/src/inference.h +++ b/icpp_llama2/src/inference.h @@ -8,5 +8,6 @@ void inference() WASM_SYMBOL_EXPORTED("canister_update inference"); -void do_inference(IC_API &ic_api, Prompt wire_prompt, Chat *chat, - std::string *output_history, MetadataUser *metadata_user); +std::string do_inference(IC_API &ic_api, Prompt wire_prompt, Chat *chat, + std::string *output_history, + MetadataUser *metadata_user, bool *error); diff --git a/icpp_llama2/src/initialize.cpp b/icpp_llama2/src/initialize.cpp index c9f6e45..8b49e59 100644 --- a/icpp_llama2/src/initialize.cpp +++ b/icpp_llama2/src/initialize.cpp @@ -22,23 +22,33 @@ Tokenizer tokenizer; // This is an exact copy of code in this method run.c, // Modified to read the data from the uploaded tokenizer.bin bytes -void build_tokenizer(Tokenizer *t, int vocab_size) { +bool build_tokenizer(Tokenizer *t, int vocab_size, IC_API &ic_api) { if (!p_tokenizer_bytes or (p_tokenizer_bytes && p_tokenizer_bytes->vec.size() == 0)) { - std::string msg; - msg.append("ERROR: " + std::string(__func__) + - " tokenizer bytes were not yet uploaded!"); - IC_API::debug_print(msg); - IC_API::trap(msg); + std::string error_msg = "ERROR: " + std::string(__func__) + + " tokenizer bytes were not yet uploaded!"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return false; } // i should have written the vocab_size into the tokenizer file... sigh t->vocab_size = vocab_size; // malloc space to hold the scores and the strings t->vocab = (char **)malloc(vocab_size * sizeof(char *)); - if (!t->vocab) IC_API::trap("Failed to allocate memory for vocab."); + if (!t->vocab) { + std::string error_msg = "Failed to allocate memory for vocab."; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return false; + } t->vocab_scores = (float *)malloc(vocab_size * sizeof(float)); - if (!t->vocab_scores) - IC_API::trap("Failed to allocate memory for vocab_scores."); + if (!t->vocab_scores) { + std::string error_msg = "Failed to allocate memory for vocab_scores."; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return false; + } + for (int i = 0; i < 256; i++) { t->byte_pieces[i * 2] = (unsigned char)i; t->byte_pieces[i * 2 + 1] = '\0'; @@ -80,21 +90,28 @@ void build_tokenizer(Tokenizer *t, int vocab_size) { data_ptr += sizeof(int); if (len <= 0 or len > t->max_token_length) { - std::string msg; - msg.append("ERROR: Memory for tokenizer is messed up."); - msg.append("\nlen for token " + std::to_string(i) + " is " + - std::to_string(len)); - msg.append( + std::string error_msg; + error_msg.append("ERROR: Memory for tokenizer is messed up."); + error_msg.append("\nlen for token " + std::to_string(i) + " is " + + std::to_string(len)); + error_msg.append( "\nIt must be larger than 0 or less than max_token_length of " + std::to_string(t->max_token_length)); - IC_API::debug_print(msg); - IC_API::trap(msg); + + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return false; } t->vocab[i] = (char *)malloc(len + 1); - if (!t->vocab[i]) - IC_API::trap("Failed to allocate memory for vocab[" + std::to_string(i) + - "] with len = " + std::to_string(len)); + if (!t->vocab[i]) { + std::string error_msg = "Failed to allocate memory for vocab[" + + std::to_string(i) + + "] with len = " + std::to_string(len); + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return false; + } // if (fread(t->vocab[i], len, 1, file) != 1) { // fprintf(stderr, "failed read\n"); // exit(EXIT_FAILURE); @@ -109,14 +126,21 @@ void build_tokenizer(Tokenizer *t, int vocab_size) { // Do this now, and not lazily // malloc and sort the vocabulary t->sorted_vocab = (TokenIndex *)malloc(t->vocab_size * sizeof(TokenIndex)); - if (!t->sorted_vocab) - IC_API::trap("Failed to allocate memory for sorted_vocab."); + if (!t->sorted_vocab) { + std::string error_msg = "Failed to allocate memory for sorted_vocab."; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return false; + } for (int i = 0; i < t->vocab_size; i++) { t->sorted_vocab[i].str = t->vocab[i]; t->sorted_vocab[i].id = i; } qsort(t->sorted_vocab, t->vocab_size, sizeof(TokenIndex), compare_tokens); + + // All OK + return true; } // This is an exact copy of code in these methods of run.c @@ -178,7 +202,7 @@ void read_checkpoint(Config *config, TransformerWeights *weights) { memory_map_weights(weights, config, weights_ptr, shared_weights); } -void build_transformer(Transformer *t) { +bool build_transformer(Transformer *t) { // read in the Config and the Weights from the checkpoint read_checkpoint(&t->config, &t->weights); @@ -188,6 +212,8 @@ void build_transformer(Transformer *t) { // //icpp: initialize the token generation settings // reset_tokens(t); + + return true; } void initialize() { @@ -195,12 +221,15 @@ void initialize() { if (!is_canister_owner(ic_api, true)) return; build_transformer(&transformer); - build_tokenizer(&tokenizer, transformer.config.vocab_size); + if (!build_tokenizer(&tokenizer, transformer.config.vocab_size, ic_api)) + return; ready_for_inference = true; - ic_api.to_wire( - CandidTypeVariant{"Ok", CandidTypeNat16{Http::StatusCode::OK}}); + CandidTypeRecord status_code_record; + status_code_record.append("status_code", + CandidTypeNat16{Http::StatusCode::OK}); + ic_api.to_wire(CandidTypeVariant{"Ok", status_code_record}); } void print_config() { diff --git a/icpp_llama2/src/llama2.did b/icpp_llama2/src/llama2.did index b3bf545..1a176b0 100644 --- a/icpp_llama2/src/llama2.did +++ b/icpp_llama2/src/llama2.did @@ -9,11 +9,6 @@ type Prompt = record { rng_seed : nat64; }; -type Inference = variant { - Ok : text; - Err : text; -}; - type Config = record { dim : int; hidden_dim : int; @@ -24,31 +19,59 @@ type Config = record { seq_len : int; }; -type StatusCode = nat16; +// ---------------------------------------------------------- +// New approach to endpoint return values: +// -> wrap a record in a Result -type Result = variant { - Ok : StatusCode; - Err : StatusCode; +// -- +type ApiError = variant { + InvalidId; + Other : text; + StatusCode : nat16; + ZeroAddress; }; -type TextResult = variant { - Ok : text; - Err : StatusCode; +// -- +// Returned by several endpoints. +// HTTPS status code wrapped in a Record wrapped in a Result +type StatusCodeRecordResult = variant { + Err : ApiError; + Ok : StatusCodeRecord; }; +type StatusCodeRecord = record { status_code : nat16 }; -type ChatStartTime = nat64; -type ChatTotalSteps = nat64; - -type NFTWhitelistItem = record { - id : principal; - description : text; +// -- +// Returned by 'set_canister_mode' +type CanisterModeRecordResult = variant { + Err : ApiError; + Ok : InferenceRecord; +}; +type CanisterModeRecord = record { canister_mode : text }; + +// -- +// Returned by 'inference', 'nft_story_start', 'nft_story_continue' +// Section of a story, generated by a single inference call +type InferenceRecordResult = variant { + Err : ApiError; + Ok : InferenceRecord; }; +type InferenceRecord = record { inference : text }; -type NFT = record { - token_id : text; +// -- +// A story, from beginning, build from multiple inference calls +type StoryRecordResult = variant { + Err : ApiError; + Ok : StoryRecord; }; +type StoryRecord = record { story : text }; -type NFTCollection = record { +// -- +// Metadata for an NFT collection +type NFTCollectionRecordResult = variant { + Err : ApiError; + Ok : NFTCollectionRecord; +}; +type NFTCollectionRecord = record { nft_supply_cap : nat64; nft_total_supply : nat64; nft_symbol : text; @@ -56,6 +79,40 @@ type NFTCollection = record { nft_description : text; }; +// -- +// Returned by 'get_users' +type UsersRecordResult = variant { + Err : ApiError; + Ok : UsersRecord; +}; +type UsersRecord = record { + user_count : nat64; + user_ids : vec text; +}; + +// -- +// Returned by 'get_user_metadata' + +type UserMetadataRecordResult = variant { + Err : ApiError; + Ok : UserMetadataRecord; +}; +type UserMetadataRecord = record { + chats_start_time : vec nat64; + chats_total_steps : vec nat64; +}; + +// ---------------------------------------------------------- + +type NFTWhitelistRecord = record { + id : principal; + description : text; +}; + +type NFT = record { + token_id : text; +}; + // -------------------------------------------------------------------------------- // HTTP Gateway Protocol // https://internetcomputer.org/docs/current/references/http-gateway-protocol-spec#canister-http-interface @@ -110,37 +167,36 @@ type StreamingStrategy = variant { service : { // canister endpoints canister_init : () -> (); - set_canister_mode : (text) -> (); + set_canister_mode : (text) -> (CanisterModeRecordResult); health : () -> (bool) query; ready : () -> (bool) query; // LLM initialization endpoints - reset_model : () -> (Result); - reset_tokenizer : () -> (Result); - upload_model_bytes_chunk : (vec nat8) -> (Result); - upload_tokenizer_bytes_chunk : (vec nat8) -> (Result); - initialize : () -> (Result); + reset_model : () -> (StatusCodeRecordResult); + reset_tokenizer : () -> (StatusCodeRecordResult); + upload_model_bytes_chunk : (vec nat8) -> (StatusCodeRecordResult); + upload_tokenizer_bytes_chunk : (vec nat8) -> (StatusCodeRecordResult); + initialize : () -> (StatusCodeRecordResult); get_model_config : () -> (Config) query; // Chat endpoints for canister_mode=chat-principal - new_chat : () -> (Result); - inference : (Prompt) -> (Inference); + new_chat : () -> (StatusCodeRecordResult); + inference : (Prompt) -> (InferenceRecordResult); // admin endpoints whoami : () -> (text) query; - get_user_count : () -> (nat64) query; - get_user_ids : () -> (vec text) query; - get_user_metadata : (text) -> (vec ChatStartTime, vec ChatTotalSteps) query; + get_users : () -> (UsersRecordResult) query; + get_user_metadata : (text) -> (UserMetadataRecordResult) query; // http endpoints http_request : (request : HttpRequest) -> (HttpResponse) query; // nft endpoints (for canister_mode=nft-ordinal) - nft_whitelist : (NFTWhitelistItem) -> (Result); - nft_init : (NFTCollection) -> (Result); - nft_metadata : () -> (NFTCollection) query; - nft_mint : (NFT) -> (Result); - nft_story_start : (NFT, Prompt) -> (Inference); - nft_story_continue : (NFT, Prompt) -> (Inference); - nft_get_story : (NFT) -> (TextResult) query; + nft_whitelist : (NFTWhitelistRecord) -> (StatusCodeRecordResult); + nft_init : (NFTCollectionRecord) -> (StatusCodeRecordResult); + nft_metadata : () -> (NFTCollectionRecordResult) query; + nft_mint : (NFT) -> (StatusCodeRecordResult); + nft_story_start : (NFT, Prompt) -> (InferenceRecordResult); + nft_story_continue : (NFT, Prompt) -> (InferenceRecordResult); + nft_get_story : (NFT) -> (StoryRecordResult) query; }; diff --git a/icpp_llama2/src/nft_collection.cpp b/icpp_llama2/src/nft_collection.cpp index 0141e3f..2cb731f 100644 --- a/icpp_llama2/src/nft_collection.cpp +++ b/icpp_llama2/src/nft_collection.cpp @@ -19,6 +19,7 @@ void new_p_nft_whitelist() { ": Allocating memory for p_nft_whitelist."); p_nft_whitelist = new (std::nothrow) NFTWhitelist(); if (p_nft_whitelist == nullptr) { + // called from canister_init, so trap is correct! IC_API::trap("Allocation of p_nft_whitelist failed"); } } @@ -63,6 +64,7 @@ void new_p_nft_collection() { ": Allocating memory for p_nft_collection."); p_nft_collection = new (std::nothrow) NFTCollection(); if (p_nft_collection == nullptr) { + // called from canister_init, so trap is correct! IC_API::trap("Allocation of p_nft_collection failed"); } } @@ -81,11 +83,20 @@ void nft_whitelist() { IC_API ic_api(CanisterUpdate{std::string(__func__)}, false); // Only the canister owner is allowed to whitelist principals - if (!is_canister_owner(ic_api, false)) IC_API::trap("Access Denied"); + if (!is_canister_owner(ic_api, false)) { + std::string error_msg = "Access Denied"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } // Do not call if static memory not yet set - if (!p_nft_whitelist) - IC_API::trap("Memory for p_nft_whitelist not yet allocated"); + if (!p_nft_whitelist) { + std::string error_msg = "Memory for p_nft_whitelist not yet allocated"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } // Read the whitelist items std::string principal_str; @@ -101,8 +112,10 @@ void nft_whitelist() { item.description = description; p_nft_whitelist->whitelist.push_back(item); - ic_api.to_wire( - CandidTypeVariant{"Ok", CandidTypeNat16{Http::StatusCode::OK}}); + CandidTypeRecord status_code_record; + status_code_record.append("status_code", + CandidTypeNat16{Http::StatusCode::OK}); + ic_api.to_wire(CandidTypeVariant{"Ok", status_code_record}); } // Initialize the NFT Collection @@ -110,15 +123,28 @@ void nft_init() { IC_API ic_api(CanisterUpdate{std::string(__func__)}, false); // Only the canister owner is allowed to initialize the NFT Collection - if (!is_canister_owner(ic_api, false)) IC_API::trap("Access Denied"); + if (!is_canister_owner(ic_api, false)) { + std::string error_msg = "Access Denied"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } // Do not call if static memory not yet set - if (!p_nft_collection) - IC_API::trap("Memory for NFT Collection not yet allocated"); + if (!p_nft_collection) { + std::string error_msg = "Memory for NFT Collection not yet allocated"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } // Protect from accidential re-initialization - if (p_nft_collection->initialized) - IC_API::trap("NFT Collection is already initialized"); + if (p_nft_collection->initialized) { + std::string error_msg = "NFT Collection is already initialized"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } // Read the metadata uint64_t nft_supply_cap{0}; @@ -142,30 +168,54 @@ void nft_init() { p_nft_collection->initialized = true; - ic_api.to_wire( - CandidTypeVariant{"Ok", CandidTypeNat16{Http::StatusCode::OK}}); + CandidTypeRecord status_code_record; + status_code_record.append("status_code", + CandidTypeNat16{Http::StatusCode::OK}); + ic_api.to_wire(CandidTypeVariant{"Ok", status_code_record}); } // Get metadata of the NFT Collection void nft_metadata() { IC_API ic_api(CanisterQuery{std::string(__func__)}, false); - if (!p_nft_collection) - IC_API::trap("Memory for NFT Collection not yet allocated"); - - if (!p_nft_collection->initialized) - IC_API::trap("NFT Collection not yet initialized"); - - // Return a record summarizing the NFTCollection - CandidTypeRecord r_out; - r_out.append("nft_supply_cap", CandidTypeNat64(p_nft_collection->supply_cap)); - r_out.append("nft_total_supply", - CandidTypeNat64(p_nft_collection->nfts.size())); - r_out.append("nft_symbol", CandidTypeText(p_nft_collection->symbol)); - r_out.append("nft_name", CandidTypeText(p_nft_collection->name)); - r_out.append("nft_description", - CandidTypeText(p_nft_collection->description)); - ic_api.to_wire(r_out); + if (!p_nft_collection) { + std::string error_msg = "Memory for NFT Collection not yet allocated"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } + + if (!p_nft_collection->initialized) { + std::string error_msg = "NFT Collection not yet initialized"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } + + // Return a NFTCollectionRecordResult + CandidTypeRecord nft_collection_record; + nft_collection_record.append("nft_supply_cap", + CandidTypeNat64(p_nft_collection->supply_cap)); + nft_collection_record.append("nft_total_supply", + CandidTypeNat64(p_nft_collection->nfts.size())); + nft_collection_record.append("nft_symbol", + CandidTypeText(p_nft_collection->symbol)); + nft_collection_record.append("nft_name", + CandidTypeText(p_nft_collection->name)); + nft_collection_record.append("nft_description", + CandidTypeText(p_nft_collection->description)); + ic_api.to_wire( + CandidTypeVariant{"Ok", CandidTypeRecord{nft_collection_record}}); +} + +// Checks if an NFT exists in the collection +bool nft_exists_(const std::string &token_id) { + for (const auto &existing_nft : p_nft_collection->nfts) { + if (existing_nft.token_id == token_id) { + return true; + } + } + return false; } // Create an NFT from the user's currently active chat @@ -173,7 +223,12 @@ void nft_mint() { IC_API ic_api(CanisterUpdate{std::string(__func__)}, false); // Only the canister owner is allowed to mint NFTs - if (!is_canister_owner(ic_api, false)) IC_API::trap("Access Denied"); + if (!is_canister_owner(ic_api, false)) { + std::string error_msg = "Access Denied"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } // The token_id is passed by argument std::string token_id; @@ -183,15 +238,20 @@ void nft_mint() { // Have we reached the limit? if (p_nft_collection->nfts.size() >= p_nft_collection->supply_cap) { - IC_API::trap("Cannot mint, reached supply_cap of " + - std::to_string(p_nft_collection->supply_cap)); + std::string error_msg = "Cannot mint, reached supply_cap of " + + std::to_string(p_nft_collection->supply_cap); + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; } - // Is there already an NFT for this ordinal? - for (const auto &existing_nft : p_nft_collection->nfts) { - if (existing_nft.token_id == token_id) { - IC_API::trap("An NFT for the token_id " + token_id + " already exists."); - } + // Is there already an NFT for this token_id? + if (nft_exists_(token_id)) { + std::string error_msg = + "An NFT for the token_id " + token_id + " already exists."; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; } // Create the NFT with an empty story: @@ -203,8 +263,10 @@ void nft_mint() { // Store the NFT in the NFTCollection p_nft_collection->nfts.push_back(nft); - ic_api.to_wire( - CandidTypeVariant{"Ok", CandidTypeNat16{Http::StatusCode::OK}}); + CandidTypeRecord status_code_record; + status_code_record.append("status_code", + CandidTypeNat16{Http::StatusCode::OK}); + ic_api.to_wire(CandidTypeVariant{"Ok", status_code_record}); } // Endpoints for the story of an NFT, callable by whitelisted principals only @@ -213,8 +275,19 @@ void nft_story_continue() { nft_story_(false); } void nft_story_(bool story_start) { IC_API ic_api(CanisterUpdate{std::string(__func__)}, false); - if (!is_canister_mode_nft_ordinal()) IC_API::trap("Access Denied"); - if (!nft_is_whitelisted(ic_api, false)) IC_API::trap("Access Denied"); + if (!is_canister_mode_nft_ordinal()) { + std::string error_msg = "Access Denied - Canister is not in NFT mode."; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } + if (!nft_is_whitelisted(ic_api, false)) { + std::string error_msg = + "Access Denied - You are not authorized to call this function."; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } if (!is_ready_and_authorized(ic_api)) return; // Get the token_id & Prompt from the wire @@ -240,13 +313,40 @@ void nft_story_(bool story_start) { if (story_start or (p_chats && p_chats->umap.find(token_id) == p_chats->umap.end())) { // Does not yet exist - build_new_chat(token_id); + if (!build_new_chat(token_id, ic_api)) return; } Chat *chat = &p_chats->umap[token_id]; std::string *output_history = &p_chats_output_history->umap[token_id]; MetadataUser *metadata_user = &p_metadata_users->umap[token_id]; - do_inference(ic_api, wire_prompt, chat, output_history, metadata_user); + bool error{false}; + std::string output = do_inference(ic_api, wire_prompt, chat, output_history, + metadata_user, &error); + + if (error) { + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{output}}}); + return; + } + + // IC_API::debug_print(output); + // Send the generated response to the wire + CandidTypeRecord inference_record; + inference_record.append("inference", CandidTypeText{output}); + ic_api.to_wire(CandidTypeVariant{"Ok", CandidTypeRecord{inference_record}}); +} + +// Checks if a story exists for an NFT in the collection +bool nft_story_exists_(const std::string &token_id) { + if ((p_chats && p_chats->umap.find(token_id) != p_chats->umap.end()) && + (p_chats_output_history && p_chats_output_history->umap.find(token_id) != + p_chats_output_history->umap.end())) { + std::string story = p_chats_output_history->umap[token_id]; + if (!story.empty()) { + return true; + } + } + return false; } // For an NFT get the story @@ -259,15 +359,20 @@ void nft_get_story() { r_in.append("token_id", CandidTypeText{&token_id}); ic_api.from_wire(r_in); - if (p_chats && p_chats->umap.find(token_id) == p_chats->umap.end()) { - IC_API::trap("NFT " + token_id + " does not exists."); + std::string error_msg; + if (!nft_exists_(token_id)) { + error_msg = "NFT " + token_id + " does not exists."; + } else if (!nft_story_exists_(token_id)) { + error_msg = "The story for NFT " + token_id + " does not exists."; } - - if (p_chats_output_history && p_chats_output_history->umap.find(token_id) == - p_chats_output_history->umap.end()) { - IC_API::trap("The story for NFT " + token_id + " does not exists."); + if (!error_msg.empty()) { + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; } - ic_api.to_wire(CandidTypeVariant{ - "Ok", CandidTypeText{p_chats_output_history->umap[token_id]}}); + CandidTypeRecord story_record; + story_record.append("story", + CandidTypeText{p_chats_output_history->umap[token_id]}); + ic_api.to_wire(CandidTypeVariant{"Ok", story_record}); } \ No newline at end of file diff --git a/icpp_llama2/src/nft_collection.h b/icpp_llama2/src/nft_collection.h index ab33238..b288f57 100644 --- a/icpp_llama2/src/nft_collection.h +++ b/icpp_llama2/src/nft_collection.h @@ -59,3 +59,5 @@ bool nft_is_whitelisted(IC_API &ic_api, bool err_to_wire = true); void new_p_nft_collection(); void delete_p_nft_collection(); void nft_story_(bool story_start); +bool nft_exists_(const std::string &token_id); +bool nft_story_exists_(const std::string &token_id); diff --git a/icpp_llama2/src/run.c b/icpp_llama2/src/run.c index be48369..0d53ab9 100644 --- a/icpp_llama2/src/run.c +++ b/icpp_llama2/src/run.c @@ -115,7 +115,7 @@ bool malloc_run_state(RunState* s, Config* p) { // ICPP: The calling function will trap the canister with a message // printf("malloc failed!\n"); // exit(1); - return false; + return false; // ICPP: caller with return Err } return true; // ICPP: all is ok } @@ -484,7 +484,12 @@ int str_lookup(char *str, TokenIndex *sorted_vocab, int vocab_size) { return res != NULL ? res->id : -1; } -void encode(Tokenizer* t, const char *text, int bos, int eos, int *tokens, int *n_tokens) { +void encode(Tokenizer* t, const char *text, int bos, int eos, int *tokens, int *n_tokens, int *error_code) { + // DEBUG TEST START - pretend this error happens... + // *error_code = 2; + // return; // ICPP: allocation of str_buffer in 'encode' function of LLM failed. + // DEBUG TEST END + // encode the string text (input) into an upper-bound preallocated tokens[] array // bos != 0 means prepend the BOS token (=1), eos != 0 means append the EOS token (=2) @@ -493,6 +498,7 @@ void encode(Tokenizer* t, const char *text, int bos, int eos, int *tokens, int * // ICPP: We made sure this does not happen when calling encode. if (text == NULL) { + *error_code = 1; return; // fprintf(stderr, "cannot encode NULL text\n"); exit(EXIT_FAILURE); } @@ -512,8 +518,10 @@ void encode(Tokenizer* t, const char *text, int bos, int eos, int *tokens, int * // create a temporary buffer that will store merge candidates of always two consecutive tokens // *2 for concat, +1 for null terminator +2 for UTF8 (in case max_token_length is 1) char* str_buffer = malloc((t->max_token_length*2 +1 +2) * sizeof(char)); - if (!str_buffer) - return; // ICPP: allocation failed. Just return and it will trap + if (!str_buffer) { + *error_code = 2; + return; // ICPP: allocation of str_buffer in 'encode' function of LLM failed. + } size_t str_len = 0; // add optional BOS (=1) token, if desired diff --git a/icpp_llama2/src/run.h b/icpp_llama2/src/run.h index afa9b88..ee36390 100644 --- a/icpp_llama2/src/run.h +++ b/icpp_llama2/src/run.h @@ -122,7 +122,7 @@ bool malloc_run_state(RunState *s, Config *p); void memory_map_weights(TransformerWeights *w, Config *p, float *ptr, int shared_weights); void encode(Tokenizer *t, const char *text, int bos, int eos, int *tokens, - int *n_tokens); + int *n_tokens, int *error_code); float *forward(Chat *chat, Transformer *transformer, int token, int pos); char *decode(Tokenizer *t, int prev_token, int token); void build_sampler(Sampler *sampler, int vocab_size, float temperature, diff --git a/icpp_llama2/src/upload.cpp b/icpp_llama2/src/upload.cpp index 092a4d7..9c324b0 100644 --- a/icpp_llama2/src/upload.cpp +++ b/icpp_llama2/src/upload.cpp @@ -17,15 +17,19 @@ TokenizerBytes *p_tokenizer_bytes{nullptr}; // 2 - a lot int DEBUG_VERBOSE = 1; -void new_model_bytes_memory() { +bool new_model_bytes_memory(IC_API &ic_api) { if (p_model_bytes == nullptr) { IC_API::debug_print(std::string(__func__) + ": Creating ModelBytes Instance."); p_model_bytes = new (std::nothrow) ModelBytes(); if (p_model_bytes == nullptr) { - IC_API::trap("Allocation of p_model_bytes failed"); + std::string error_msg = "Allocation of p_model_bytes failed"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return false; } } + return true; } void delete_model_bytes_memory() { @@ -35,15 +39,19 @@ void delete_model_bytes_memory() { } } -void new_tokenizer_bytes_memory() { +bool new_tokenizer_bytes_memory(IC_API &ic_api) { if (p_tokenizer_bytes == nullptr) { IC_API::debug_print(std::string(__func__) + ": Creating TokenizerBytes Instance."); p_tokenizer_bytes = new (std::nothrow) TokenizerBytes(); if (p_tokenizer_bytes == nullptr) { - IC_API::trap("Allocation of p_tokenizer_bytes failed"); + std::string error_msg = "Allocation of p_tokenizer_bytes failed"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return false; } } + return true; } void delete_tokenizer_bytes_memory() { @@ -107,8 +115,10 @@ void reset_model() { delete_model_bytes_memory(); - ic_api.to_wire( - CandidTypeVariant{"Ok", CandidTypeNat16{Http::StatusCode::OK}}); + CandidTypeRecord status_code_record; + status_code_record.append("status_code", + CandidTypeNat16{Http::StatusCode::OK}); + ic_api.to_wire(CandidTypeVariant{"Ok", status_code_record}); } void reset_tokenizer() { @@ -119,8 +129,10 @@ void reset_tokenizer() { delete_tokenizer_bytes_memory(); - ic_api.to_wire( - CandidTypeVariant{"Ok", CandidTypeNat16{Http::StatusCode::OK}}); + CandidTypeRecord status_code_record; + status_code_record.append("status_code", + CandidTypeNat16{Http::StatusCode::OK}); + ic_api.to_wire(CandidTypeVariant{"Ok", status_code_record}); } // Endpoint for uploading the stories15M.bin file as bytes @@ -131,13 +143,17 @@ void upload_model_bytes_chunk() { std::vector v; ic_api.from_wire(CandidTypeVecNat8{&v}); - if (p_model_bytes == nullptr) new_model_bytes_memory(); + if (p_model_bytes == nullptr) { + if (!new_model_bytes_memory(ic_api)) return; + } p_model_bytes->vec.insert(p_model_bytes->vec.end(), v.begin(), v.end()); print_upload_model_bytes_summary(std::string(__func__), v); - ic_api.to_wire( - CandidTypeVariant{"Ok", CandidTypeNat16{Http::StatusCode::OK}}); + CandidTypeRecord status_code_record; + status_code_record.append("status_code", + CandidTypeNat16{Http::StatusCode::OK}); + ic_api.to_wire(CandidTypeVariant{"Ok", status_code_record}); } // Endpoint for uploading the tokenizer.bin file as bytes @@ -148,12 +164,16 @@ void upload_tokenizer_bytes_chunk() { std::vector v; ic_api.from_wire(CandidTypeVecNat8{&v}); - if (p_tokenizer_bytes == nullptr) new_tokenizer_bytes_memory(); + if (p_tokenizer_bytes == nullptr) { + if (!new_tokenizer_bytes_memory(ic_api)) return; + } p_tokenizer_bytes->vec.insert(p_tokenizer_bytes->vec.end(), v.begin(), v.end()); print_upload_tokenizer_bytes_summary(std::string(__func__), v); - ic_api.to_wire( - CandidTypeVariant{"Ok", CandidTypeNat16{Http::StatusCode::OK}}); + CandidTypeRecord status_code_record; + status_code_record.append("status_code", + CandidTypeNat16{Http::StatusCode::OK}); + ic_api.to_wire(CandidTypeVariant{"Ok", status_code_record}); } \ No newline at end of file diff --git a/icpp_llama2/src/users.cpp b/icpp_llama2/src/users.cpp index 763666d..b338913 100644 --- a/icpp_llama2/src/users.cpp +++ b/icpp_llama2/src/users.cpp @@ -17,36 +17,43 @@ void whoami() { ic_api.to_wire(CandidTypeText{caller.get_text()}); } -void get_user_count() { +void get_users() { IC_API ic_api(CanisterQuery{std::string(__func__)}, false); - if (!is_canister_owner(ic_api, false)) IC_API::trap("Access Denied"); + if (!is_canister_owner(ic_api, false)) { + std::string error_msg = "Access Denied"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } + // Get the number of users uint64_t user_count = 0; if (p_metadata_users) { user_count = p_metadata_users->umap.size(); } - ic_api.to_wire(CandidTypeNat64{user_count}); -} - -void get_user_ids() { - IC_API ic_api(CanisterQuery{std::string(__func__)}, false); - if (!is_canister_owner(ic_api, false)) IC_API::trap("Access Denied"); - + // Get the user ids std::vector user_ids; - if (p_metadata_users) { for (const auto &pair : p_metadata_users->umap) { user_ids.push_back(pair.first); } } - ic_api.to_wire(CandidTypeVecText{user_ids}); + CandidTypeRecord users_record; + users_record.append("user_count", CandidTypeNat64{user_count}); + users_record.append("user_ids", CandidTypeVecText{user_ids}); + ic_api.to_wire(CandidTypeVariant{"Ok", users_record}); } void get_user_metadata() { IC_API ic_api(CanisterQuery{std::string(__func__)}, false); - if (!is_canister_owner(ic_api, false)) IC_API::trap("Access Denied"); + if (!is_canister_owner(ic_api, false)) { + std::string error_msg = "Access Denied"; + ic_api.to_wire(CandidTypeVariant{ + "Err", CandidTypeVariant{"Other", CandidTypeText{error_msg}}}); + return; + } std::string in_principal; ic_api.from_wire(CandidTypeText{&in_principal}); @@ -63,8 +70,10 @@ void get_user_metadata() { } } - CandidArgs args_out; - args_out.append(CandidTypeVecNat64{chats_start_time}); - args_out.append(CandidTypeVecNat64{chats_total_steps}); - ic_api.to_wire(args_out); + CandidTypeRecord user_metadata_record; + user_metadata_record.append("chats_start_time", + CandidTypeVecNat64{chats_start_time}); + user_metadata_record.append("chats_total_steps", + CandidTypeVecNat64{chats_total_steps}); + ic_api.to_wire(CandidTypeVariant{"Ok", user_metadata_record}); } \ No newline at end of file diff --git a/icpp_llama2/src/users.h b/icpp_llama2/src/users.h index 2442734..92ccc5d 100644 --- a/icpp_llama2/src/users.h +++ b/icpp_llama2/src/users.h @@ -7,7 +7,6 @@ void whoami() WASM_SYMBOL_EXPORTED("canister_query whoami"); // admin functions -void get_user_count() WASM_SYMBOL_EXPORTED("canister_query get_user_count"); -void get_user_ids() WASM_SYMBOL_EXPORTED("canister_query get_user_ids"); +void get_users() WASM_SYMBOL_EXPORTED("canister_query get_users"); void get_user_metadata() WASM_SYMBOL_EXPORTED("canister_query get_user_metadata"); \ No newline at end of file diff --git a/icpp_llama2/test/test_apis.py b/icpp_llama2/test/test_apis.py index 57dc63f..5f39ebe 100644 --- a/icpp_llama2/test/test_apis.py +++ b/icpp_llama2/test/test_apis.py @@ -70,7 +70,7 @@ def test__reset_model_err(identity_anonymous: dict[str, str], network: str) -> N network=network, timeout_seconds=10, ) - expected_response = "(variant { Err = 401 : nat16 })" + expected_response = '(variant { Err = variant { Other = "Access Denied" } })' assert response == expected_response @@ -140,30 +140,17 @@ def test__inference_2(identity_default: dict[str, str], network: str) -> None: # Users data, requires that identity_default is the owner # So, deploy with that default identity !!! # -def test__get_user_count(identity_default: dict[str, str], network: str) -> None: +def test__get_users(identity_default: dict[str, str], network: str) -> None: response = call_canister_api( dfx_json_path=DFX_JSON_PATH, canister_name=CANISTER_NAME, - canister_method="get_user_count", - canister_argument="4449444c0000", - canister_input="raw", - canister_output="raw", - network=network, - ) - assert "4449444c0001780100000000000000" == response - - -def test__get_user_ids(identity_default: dict[str, str], network: str) -> None: - _response = call_canister_api( - dfx_json_path=DFX_JSON_PATH, - canister_name=CANISTER_NAME, - canister_method="get_user_ids", - canister_argument="4449444c0000", - canister_input="raw", - canister_output="raw", + canister_method="get_users", + canister_argument="()", network=network, ) - # No assert. A test just to make sure it does not trap. + principal = identity_default["principal"] + expected_response = f'(variant {{ Ok = record {{ user_count = 1 : nat64; user_ids = vec {{ "{principal}";}};}} }})' + assert response == expected_response def test__get_user_metadata(identity_default: dict[str, str], network: str) -> None: @@ -176,44 +163,30 @@ def test__get_user_metadata(identity_default: dict[str, str], network: str) -> N canister_output="raw", network=network, ) - # No assert. A test just to make sure it does not trap. + # No assert. A test just to make sure it returns. # ---------------------------------------------------------------------------------- -# Trap testing +# Err testing # -# Verify that calls trap when not owner -def test__trap_get_user_count(identity_anonymous: dict[str, str], network: str) -> None: +# Verify that Err when not owner +def test__err_get_users(identity_anonymous: dict[str, str], network: str) -> None: response = call_canister_api( dfx_json_path=DFX_JSON_PATH, canister_name=CANISTER_NAME, - canister_method="get_user_count", + canister_method="get_users", canister_argument="4449444c0000", canister_input="raw", canister_output="raw", network=network, ) - assert "Failed call to api" in response - assert "trapped explicitly" in response - assert "Access Denied" in response - - -def test__trap_get_user_ids(identity_anonymous: dict[str, str], network: str) -> None: - response = call_canister_api( - dfx_json_path=DFX_JSON_PATH, - canister_name=CANISTER_NAME, - canister_method="get_user_ids", - canister_argument="4449444c0000", - canister_input="raw", - canister_output="raw", - network=network, + assert ( + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100000d4163636573732044656e696564" + == response ) - assert "Failed call to api" in response - assert "trapped explicitly" in response - assert "Access Denied" in response -def test__trap_get_user_metadata( +def test__err_get_user_metadata( identity_anonymous: dict[str, str], network: str ) -> None: response = call_canister_api( @@ -225,6 +198,7 @@ def test__trap_get_user_metadata( canister_output="raw", network=network, ) - assert "Failed call to api" in response - assert "trapped explicitly" in response - assert "Access Denied" in response + assert ( + "4449444c026b01b0ad8fcd0c716b01c5fed20100010100000d4163636573732044656e696564" + == response + )