Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ check_untyped_defs = True
disallow_untyped_decorators = True
show_error_codes = True
warn_redundant_casts = True
warn_unused_ignores = True
warn_unused_ignores = True

# icp_core does not ship py.typed or type stubs
[mypy-icp_core]
ignore_missing_imports = True
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local,numpy.random,matplo
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
ignored-modules=icp_core

# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ VERSION_DIDC := $(shell curl --silent "https://api.github.com/repos/dfinity/cand
# version to install for clang
VERSION_CLANG := $(shell cat version_clang.txt)
# version to install for dfx
VERSION_DFX := 0.29.2
VERSION_DFX := 0.31.0

###########################################################################
# Use some clang tools that come with wasi-sdk
Expand Down
49 changes: 49 additions & 0 deletions README-Release-Guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Release Guide

How to create a new release of `llama_cpp_canister`.

## Prerequisites

- The latest `cicd-mac` workflow run on the `main` branch must have succeeded.
The release workflow checks this automatically and will fail if CI/CD is red.
- All changes intended for the release are merged into `main`.

## Steps

### 1. Bump the version

Update `version.txt` in the repo root to the desired release version (e.g. `0.8.0`).
Commit and push to `main`.

### 2. Trigger the release workflow

1. Go to **Actions** > **Release llama_cpp_canister** in the GitHub UI.
2. Click **Run workflow**.
3. Enter the tag for the release (e.g. `v0.8.0`).
4. Click **Run workflow** to start.

### 3. What the workflow does

| Step | Description |
| --------------------------- | ----------------------------------------------------------------------------- |
| **check-cicd-mac-status** | Verifies the latest `cicd-mac.yml` run succeeded |
| **install & build** | Sets up miniconda, installs toolchains, builds the Wasm canister |
| **zip release files** | Packages `build/`, `scripts/`, `test/`, `dfx.json`, `version.txt`, etc. |
| **create GitHub release** | Creates a GitHub release with tag and attaches `llama_cpp_canister_<tag>.zip` |

### 4. Post-release verification

After the workflow completes:

1. Download the zip artifact from the GitHub release page.
2. Unzip and verify the contents include:
- `build/llama_cpp.wasm` and `build/llama_cpp.did`
- `scripts/` with upload/download tooling
- `test/` with smoke tests
- `dfx.json`, `version.txt`, `requirements.txt`
3. Optionally deploy and run smoke tests:
```bash
dfx start --clean --background
dfx deploy --network local
pytest -vv --network local test/
```
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,21 @@ You can just grab the latest [release](https://github.com/onicai/llama_cpp_canis

# Set up

- Install dfx:
- Install dfx (version 0.31.0 or later is required):

```bash
sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"

# Configure your shell
source "$HOME/.local/share/dfx/env"

# Verify the version (must be >= 0.31.0)
dfx --version
```

> **Note:** dfx 0.31+ is required because `icp-py-core` uses the `/api/v3/`
> endpoint, which is not supported by older dfx versions.

- Clone the repo and it's children:

_(skip when using the [release](https://github.com/onicai/llama_cpp_canister/releases))_
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
-r scripts/requirements.txt
-r src/llama_cpp_onicai_fork/requirements.txt
icpp-pro>=5.3.1
ic-py==1.0.1
icp-py-core==2.3.0
binaryen.py
20 changes: 11 additions & 9 deletions scripts/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import sys
from pathlib import Path
from typing import List
from .ic_py_canister import get_canister, run_dfx_command
from .ic_py_canister import extract_variant, get_canister, run_dfx_command
from .parse_args_download import parse_args
from .calculate_sha256 import calculate_sha256

Expand Down Expand Up @@ -64,13 +64,14 @@ def main() -> int:
)

# ---------------------------------------------------------------------------
# get ic-py based Canister instance
# get icp-py-core based Canister instance
canister_instance = get_canister(canister_name, candid_path, network, canister_id)

# check health (liveness)
print("--\nChecking liveness of canister (did we deploy it!)")
response = canister_instance.health()
if "Ok" in response[0].keys():
result = extract_variant(response)
if "Ok" in result:
print("Ok!")
else:
print("Not OK, response is:")
Expand Down Expand Up @@ -104,11 +105,12 @@ def main() -> int:
}
)

if "Ok" in response[0].keys():
r_filesize = response[0]["Ok"]["filesize"]
r_chunk: List[int] = response[0]["Ok"]["chunk"]
r_chunksize = response[0]["Ok"]["chunksize"]
r_offset = response[0]["Ok"]["offset"]
result = extract_variant(response)
if "Ok" in result:
r_filesize = result["Ok"]["filesize"]
r_chunk: List[int] = result["Ok"]["chunk"]
r_chunksize = result["Ok"]["chunksize"]
r_offset = result["Ok"]["offset"]
total_received = offset + len(r_chunk)
print(
"--"
Expand All @@ -123,7 +125,7 @@ def main() -> int:

f.write(bytearray(r_chunk))

done = response[0]["Ok"]["done"]
done = result["Ok"]["done"]
else:
print("Something went wrong:")
print(response)
Expand Down
28 changes: 19 additions & 9 deletions scripts/ic_py_canister.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
"""Returns the ic-py Canister instance, for calling the endpoints."""
"""Returns the icp-py-core Canister instance, for calling the endpoints."""

import sys
import subprocess
from pathlib import Path
from typing import Optional
from ic.canister import Canister # type: ignore
from ic.client import Client # type: ignore
from ic.identity import Identity # type: ignore
from ic.agent import Agent # type: ignore
from typing import Any, List, Optional
from icp_core import Agent, Identity, Client, Canister
from icpp.run_shell_cmd import run_shell_cmd

ROOT_PATH = Path(__file__).parent.parent
Expand All @@ -16,6 +13,19 @@
DFX = "dfx"


def extract_variant(response: List[Any]) -> Any:
"""Extract variant result from icp-py-core response.

icp-py-core returns: [{'type': 'variant', 'value': {'Ok': {...}}}]
old ic-py returned: [{'Ok': {...}}]
This helper normalizes both formats to {'Ok': {...}} or {'Err': ...}.
"""
item = response[0]
if "value" in item:
return item["value"]
return item


def run_dfx_command(cmd: str, quiet: bool = False) -> Optional[str]:
"""Runs dfx command as a subprocess"""
try:
Expand All @@ -27,7 +37,7 @@ def run_dfx_command(cmd: str, quiet: bool = False) -> Optional[str]:


def get_agent(network: str = "local") -> Agent:
"""Returns an ic_py Agent instance"""
"""Returns an icp-py-core Agent instance"""

# Check if the network is up
print(f"--\nChecking if the {network} network is up...")
Expand Down Expand Up @@ -82,7 +92,7 @@ def get_canister(
network: str = "local",
canister_id: Optional[str] = "",
) -> Canister:
"""Returns an ic_py Canister instance"""
"""Returns an icp-py-core Canister instance"""

agent = get_agent(network=network)

Expand All @@ -104,4 +114,4 @@ def get_canister(
canister_did = f.read()

# Create a Canister instance
return Canister(agent=agent, canister_id=canister_id, candid=canister_did)
return Canister(agent=agent, canister_id=canister_id, candid_str=canister_did)
39 changes: 22 additions & 17 deletions scripts/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from pathlib import Path
from typing import Generator
from .calculate_sha256 import calculate_sha256
from .ic_py_canister import get_canister, run_dfx_command
from .ic_py_canister import extract_variant, get_canister, run_dfx_command
from .parse_args_upload import parse_args

ROOT_PATH = Path(__file__).parent.parent
Expand Down Expand Up @@ -88,13 +88,14 @@ def main() -> int:
)

# ---------------------------------------------------------------------------
# get ic-py based Canister instance
# get icp-py-core based Canister instance
canister_instance = get_canister(canister_name, candid_path, network, canister_id)

# check health (liveness)
print("--\nChecking liveness of canister (did we deploy it!)")
response = canister_instance.health()
if "Ok" in response[0].keys():
result = extract_variant(response)
if "Ok" in result:
print("Ok!")
else:
print("Not OK, response is:")
Expand Down Expand Up @@ -163,7 +164,8 @@ def main() -> int:
"chunk": chunk,
"chunksize": chunksize,
"offset": offset,
}
},
verify_certificate=False,
) # pylint: disable=no-member
else:
response = canister_instance.file_upload_chunk(
Expand All @@ -172,7 +174,8 @@ def main() -> int:
"chunk": chunk,
"chunksize": chunksize,
"offset": offset,
}
},
verify_certificate=False,
) # pylint: disable=no-member

break # Exit the loop if the request is successful
Expand All @@ -187,24 +190,25 @@ def main() -> int:
print(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay) # Wait before retrying

if "Ok" in response[0].keys():
result = extract_variant(response)
if "Ok" in result:
if DEBUG_VERBOSE == 0:
pass
elif DEBUG_VERBOSE == 1:
# print only every 10th chunk or if it is the last chunk
if i % 10 == 0 or (offset + len(chunk)) >= len(file_bytes):
print(
f"OK! filesize = {response[0]['Ok']['filesize']}, "
f"filesha256 = {response[0]['Ok']['filesha256']}"
f"OK! filesize = {result['Ok']['filesize']}, "
f"filesha256 = {result['Ok']['filesha256']}"
)
else:
print(
f"OK! filesize = {response[0]['Ok']['filesize']}, "
f"filesha256 = {response[0]['Ok']['filesha256']}"
f"OK! filesize = {result['Ok']['filesize']}, "
f"filesha256 = {result['Ok']['filesha256']}"
)

canister_filesize = response[0]["Ok"]["filesize"]
canister_filesha256 = response[0]["Ok"]["filesha256"]
canister_filesize = result["Ok"]["filesize"]
canister_filesha256 = result["Ok"]["filesha256"]
else:
print("Something went wrong:")
print(response)
Expand Down Expand Up @@ -245,14 +249,15 @@ def main() -> int:
{"filename": canister_filename}
)

if "Ok" in response[0].keys():
result = extract_variant(response)
if "Ok" in result:
print(
f"OK! filesize = {response[0]['Ok']['filesize']}, "
f"filesha256 = {response[0]['Ok']['filesha256']}"
f"OK! filesize = {result['Ok']['filesize']}, "
f"filesha256 = {result['Ok']['filesha256']}"
)

canister_filesize = response[0]["Ok"]["filesize"]
canister_filesha256 = response[0]["Ok"]["filesha256"]
canister_filesize = result["Ok"]["filesize"]
canister_filesha256 = result["Ok"]["filesha256"]

if (canister_filesize != local_file_size) or (
canister_filesha256 != local_file_sha256
Expand Down
Loading