├── .github └── workflows │ └── release.yml ├── .gitignore ├── README.md ├── blog-recommendation-ts ├── .gitignore ├── README.md ├── blog_list.txt ├── migrations.sql ├── package-lock.json ├── package.json ├── runtime-config-template.toml ├── spin.toml ├── src │ ├── index.ts │ └── utils.ts ├── tsconfig.json ├── update_embeddings.sh └── webpack.config.js ├── code-generator-rs ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── api │ ├── Cargo.lock │ ├── Cargo.toml │ ├── spin.toml │ └── src │ │ └── lib.rs └── client │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── main.rs ├── haiku-generator-assets ├── dynamic.js └── index.html ├── haiku-generator-rs ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── spin.toml └── src │ └── lib.rs ├── haiku-generator-ts ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── spin.toml ├── src │ └── index.ts ├── tsconfig.json └── webpack.config.js ├── helloworld-py ├── .gitignore ├── Pipfile ├── README.md ├── app.py └── spin.toml ├── helloworld-rs ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── spin.toml └── src │ └── lib.rs ├── helloworld-ts ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── runtime-config.toml ├── spin.toml ├── src │ └── index.ts ├── tsconfig.json └── webpack.config.js ├── newsfeeder-ts ├── .gitignore ├── README.md ├── newsfeeder.yaml ├── package-lock.json ├── package.json ├── spin.toml ├── src │ └── index.ts ├── tsconfig.json └── webpack.config.js ├── openapi-rs ├── .gitignore ├── README.md ├── api │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── openapi.yml ├── spin.toml └── swagger-ui │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── index.css │ ├── index.html │ ├── oauth2-redirect.html │ ├── swagger-initializer.js │ ├── swagger-ui-bundle.js │ ├── swagger-ui-bundle.js.map │ ├── swagger-ui-es-bundle-core.js │ ├── swagger-ui-es-bundle-core.js.map │ ├── swagger-ui-es-bundle.js │ ├── swagger-ui-es-bundle.js.map │ ├── swagger-ui-standalone-preset.js │ ├── swagger-ui-standalone-preset.js.map │ ├── swagger-ui.css │ ├── swagger-ui.css.map │ ├── swagger-ui.js │ └── swagger-ui.js.map ├── sentiment-analysis-assets ├── dynamic.js └── index.html ├── sentiment-analysis-py ├── .gitignore ├── README.md ├── app.py ├── assets │ ├── dynamic.js │ └── index.html ├── requirements.txt └── spin.toml ├── sentiment-analysis-rs ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── assets │ ├── dynamic.js │ └── index.html ├── spin.toml └── src │ └── lib.rs ├── sentiment-analysis-ts ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── spin.toml ├── src │ └── index.ts ├── tsconfig.json └── webpack.config.js └── silly-walk-ts ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── spin.toml ├── src └── index.ts ├── tsconfig.json └── webpack.config.js /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Artifacts for AI examples 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | app: 6 | type: choice 7 | description: Which app to build and push 8 | options: 9 | - newsfeeder-ts 10 | tag: 11 | type: string 12 | description: 'new tag for this push' 13 | required: true 14 | 15 | env: 16 | REGISTRY: ghcr.io 17 | 18 | jobs: 19 | echo-inputs: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Echo Inputs 23 | run: | 24 | echo app: ${{ github.event.inputs.app }} 25 | echo tag: ${{ github.event.inputs.tag }} 26 | 27 | build-and-push: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v3 31 | 32 | - uses: actions/setup-node@v3 33 | with: 34 | node-version: 19 35 | 36 | - name: setup spin 37 | uses: fermyon/actions/spin/setup@v1 38 | with: 39 | version: v2.0.0 40 | plugins: js2wasm 41 | github_token: ${{ github.token }} 42 | 43 | - name: npm install 44 | run: npm install --prefix ${{ github.event.inputs.app }} 45 | 46 | - name: build and push 47 | uses: fermyon/actions/spin/push@v1 48 | with: 49 | registry: ${{ env.REGISTRY }} 50 | registry_username: ${{ github.actor }} 51 | registry_password: ${{ secrets.GITHUB_TOKEN }} 52 | registry_reference: "${{ env.REGISTRY }}/fermyon/${{ github.event.inputs.app }}:${{ github.event.inputs.tag }}" 53 | manifest_file: ${{ github.event.inputs.app }}/spin.toml 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | **/runtime-config.toml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fermyon Serverless AI Examples 2 | 3 | This repository contains examples of using [Fermyon Serverless AI](https://developer.fermyon.com/cloud/serverless-ai). 4 | 5 | ## Prerequisites 6 | 7 | The following prerequisites are needed to build and run these examples: 8 | - Spin version 1.5.0 or newer. You can find the install and update instructions [here](https://developer.fermyon.com/spin/install#installing-spin). 9 | - The following plugins: 10 | - js2wasm 0.6.1 `spin plugins install js2wasm`. 11 | - py2wasm 0.3.2 `spin plugins install py2wasm`. 12 | - Optional, but highly recommended, is to use the [Spin Cloud GPU component](https://github.com/fermyon/spin-cloud-gpu). This offloads inferencing to Fermyon Cloud GPUs, and thus requires a free account to [Fermyon Cloud Serverless AI](https://www.fermyon.com/serverless-ai). 13 | - If you do not want to use the Cloud GPU component, the instructions on how to download the required models for inferencing and embedding can be found [here](https://developer.fermyon.com/spin/ai-sentiment-analysis-api-tutorial#application-structure). 14 | 15 | > Note that Fermyon Cloud support is currently in private beta. To apply for access to the private beta, please fill out [this application form](https://fibsu0jcu2g.typeform.com/serverless-ai). 16 | 17 | ## Examples overview 18 | 19 | Here is a table of the following examples: 20 | 21 | | Example Name | Spin SDK | Inferencing | Embedding | Vector DBs | 22 | | ------------- | ------------- | ------------- | ------------- | ------------- | 23 | | [blog-recommendation-ts](./blog-recommendation-ts/) | TypeScript | | :white_check_mark: | :white_check_mark: | 24 | | [code-generator-rs](./code-generator-rs/) | Rust | :white_check_mark: | | | 25 | | [haiku-generator-rs](./haiku-generator-rs/) | Rust | :white_check_mark: | | | 26 | | [haiku-generator-ts](./haiku-generator-ts/) | TypeScript | :white_check_mark: | | | 27 | | [newsfeeder-ts](./newsfeeder-ts/) | Typescript | :white_check_mark: | | | 28 | | [openapi-rs](./openapi-rs/) | Rust | :white_check_mark: | | | 29 | | [sentiment-analysis-py](./sentiment-analysis-py/) | Python | :white_check_mark: | | | 30 | | [sentiment-analysis-rs](./sentiment-analysis-rs/) | Rust | :white_check_mark: | | | 31 | | [sentiment-analysis-ts](./sentiment-analysis-ts/) | Typescript | :white_check_mark: | | | 32 | | [silly-walk-ts](./silly-walk-ts/) | Typescript | :white_check_mark: | | | 33 | 34 | To get started building your own applications, follow [these instructions](https://developer.fermyon.com/spin/serverless-ai-tutorial) to make sure you have installed the correct version of Spin and the necessary SDKs. If you'd like to learn more about the API, visit the [API guide here](https://developer.fermyon.com/spin/serverless-ai-api-guide). 35 | -------------------------------------------------------------------------------- /blog-recommendation-ts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | target 4 | .spin/ 5 | runtime-config.toml -------------------------------------------------------------------------------- /blog-recommendation-ts/README.md: -------------------------------------------------------------------------------- 1 | ## Blog recommendation 2 | 3 | This is a example application showcasing a recommendation system for blogs. 4 | 5 | Please check the repo's [README](../README.md#prerequisites) for prerequisites for running this example. 6 | 7 | This sample uses the Vector extension to sqlite, which is available with [Turso](https://turso.tech/). 8 | 9 | ### Steps to use 10 | 11 | - Create a [Turso database](https://turso.tech/) using personal account and get configs 12 | 13 | ```bash 14 | turso db create --enable-extensions 15 | # Get the url 16 | turso db show 17 | # Create access token 18 | turso db tokens create --expiration none 19 | ``` 20 | 21 | - make a copy of `runtime-config-template.toml` as `runtime-config.toml` and fill in the details 22 | 23 | - Shell into Turso Db to create virtual table using the `vss` module 24 | 25 | ```bash 26 | $ turso db shell 27 | - CREATE virtual TABLE vss_blog_posts USING vss0(embedding(384)); 28 | ``` 29 | 30 | ```bash 31 | $ npm install 32 | $ spin build -u --runtime-config-file runtime-config.toml --sqlite @migrations.sql 33 | ``` 34 | 35 | > Note: If you are using the Cloud GPU component, remember to add the `[llm_compute]` section to the `runtime-config.toml` file. 36 | 37 | - Create embeddings for all the posts 38 | 39 | ```bash 40 | ./update_embeddings.sh http://localhost:3000 41 | ``` 42 | 43 | - Query for similar articles 44 | 45 | ```bash 46 | curl -X POST -d '{"blogPath": "scale-to-zero-problem"}' http://localhost:3000/getRecommendations 47 | ``` 48 | 49 | ## Deploy the application to Fermyon Cloud 50 | 51 | ```bash 52 | $ spin deploy 53 | ``` 54 | -------------------------------------------------------------------------------- /blog-recommendation-ts/blog_list.txt: -------------------------------------------------------------------------------- 1 | 2022-02-08-hello-world 2 | 2023-01-12 3 | 2023-02-24-fermyon-at-the-edge 4 | 2023-06-07-integrating-with-ssgs 5 | 6-ways-to-spin 6 | advent-of-spin-2022 7 | announcing-custom-domains 8 | announcing-noops-sql-db 9 | announcing-spin-up-hub 10 | bindle-what-is-it 11 | bots-with-spin-and-fermyon-cloud 12 | build-you-own-cms-from-a-template 13 | building-a-social-app-with-spin-1 14 | building-a-social-app-with-spin-2 15 | building-a-social-app-with-spin-3-5 16 | building-a-social-app-with-spin-3 17 | building-a-social-app-with-spin-4 18 | building-host-for-spin-runtime 19 | building-with-spin-at-ossummit 20 | chinchilla-squeaks-podcast 21 | cloud-plugin 22 | cms-rich-results 23 | cncf-wasm-day-keynote-video 24 | complex-world-of-wasm-language-support 25 | component-reuse 26 | david-flanagan-interviews-matt-and-radu 27 | debugging-with-the-key-value-store-explorer 28 | dioxus-in-spin 29 | disrupting-tech-wasm-edition-india 30 | dockercon-fermyon-video 31 | dockercon 32 | dont-repatriate-servers 33 | dotnet-wasi 34 | exploring_variables 35 | fermyon-at-civo-navigate-2023 36 | fermyon-at-cphdevfest-23 37 | fermyon-at-developerweek23 38 | fermyon-at-kubeconeu23 39 | fermyon-at-wasmio-23 40 | fermyon-at-wearedevelopers-23 41 | fermyon-cloud-subscription-tier 42 | fermyon-discord 43 | fermyon-india-road-trip-retro 44 | finicky-whiskers-makes-it-to-cloud 45 | finicky-whiskers-part-1-intro 46 | finicky-whiskers-part-2-fileserver 47 | finicky-whiskers-part-3-microservices 48 | finicky-whiskers-part-4-infrastructure 49 | four-domains-wasm 50 | fun-wasm-tools 51 | github-actions-and-metrics-fermyon-cloud 52 | gluecon-recap 53 | happy-first-birthday-fermyon 54 | hashiconf-eu-2022 55 | hashiconf-eu-spin-nomad-2022 56 | how-i-built-a-like-button-for-my-blog-with-spin 57 | how-to-think-about-wasm 58 | index 59 | intro-to-the-bytecode-alliance 60 | intro-to-wasm 61 | introducing-bartholomew 62 | introducing-componentize-py 63 | introducing-fermyon-cloud-key-value-store 64 | introducing-fermyon-cloud 65 | introducing-fermyon-platform 66 | introducing-spin-v1 67 | introducing-spin-v11 68 | introducing-spin 69 | know-before-you-go-gluecon-2023 70 | know-before-you-go-oss-na 71 | kubecon-amsterdam-2023-roundup 72 | kubecon-cloudnativecon-detroit-2022 73 | kubecon-recap-fermyon-cloud-release 74 | log4sh-and-webassembly 75 | managing-spin-templates-and-plugins 76 | meet-fermyon-friend-sohan 77 | mit-sloan-cio-symposium 78 | next-generation-of-serverless-is-happening 79 | nginx-sprint-fermyon 80 | noops-and-serverless-are-the-perfect-pair 81 | open-source-startup-podcast 82 | open-source-summit-europe-fermyon-2022 83 | open-source-summit-fermyon-2022 84 | open-source-summit-na-vancouver-roundup 85 | open-source-summit 86 | optimizing-tinygo-wasm 87 | our-values 88 | paas-is-not-dead 89 | persistent-storage-in-webassembly-applications 90 | php-spin-fermyon-cloud 91 | python-wagi 92 | qt-libreoffice-wasm 93 | raising-the-limits 94 | rethinking-microservices 95 | risks-of-webassembly 96 | rust-linz-spin-component-model 97 | scale-to-zero-problem 98 | scripts-vs-compiled-wasm 99 | september-community-meeting 100 | serverless-reckoning 101 | serving-static-content-via-webassembly 102 | software-engineering-daily-podcast-fermyon 103 | spin-application-structure 104 | spin-in-docker 105 | spin-js-sdk 106 | spin-new-release 107 | spin-nomad 108 | spin-on-digital-ocean-droplet 109 | spin-python-sdk 110 | spin-release-candidate-community-bug-bash 111 | spin-rest-apis 112 | spin-v010 113 | spin-v03 114 | spin-v07 115 | spin-v08 116 | spin-v09 117 | spin-v12 118 | spin-v13 119 | spin-v14 120 | spin-watch-live-reloading 121 | spin-webhooks 122 | spinning-with-swift 123 | talking-webassembly-spin-and-fermyon-cloud-we-are-trying-something-new 124 | the-future-of-webassembly-in-the-cloud-our-glimpse 125 | tinygo-webassembly-favicon-server 126 | typescript-and-fermyon-cloud-key-value-storage 127 | underprovisioning-cloud-services 128 | untitled 129 | wasm-builders-announcement 130 | wasm-day 131 | wasm-io-2023-roundup 132 | wasm-wasi-wagi 133 | wearedevelopers-wrap-up 134 | webassembly-component-model 135 | webassembly-content-management-system-cms-search-engine-optimization-seo 136 | webassembly-for-dotnet-developers-spin-sdk-intro 137 | webassembly-languages 138 | webassembly-vs-containers 139 | why-and-how-wasm-cms-bartholomew 140 | why-wasm-excellent-cloud 141 | why-we-need-another-kind-cloud-compute 142 | -------------------------------------------------------------------------------- /blog-recommendation-ts/migrations.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS blog_posts(url TEXT PRIMARY KEY, title TEXT, description TEXT, embedding BLOB); 2 | 3 | DROP TABLE IF EXISTS vss_blog_posts; 4 | 5 | CREATE virtual TABLE vss_blog_posts USING vss0(embedding(384)); 6 | 7 | insert into vss_blog_posts(rowid,embedding) select rowid,embedding from blog_posts; 8 | -------------------------------------------------------------------------------- /blog-recommendation-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-sdk", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npx webpack --mode=production && mkdir -p target && spin js2wasm -o target/blog-recommendation.wasm dist/spin.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "ts-loader": "^9.4.1", 15 | "typescript": "^4.8.4", 16 | "webpack": "^5.74.0", 17 | "webpack-cli": "^4.10.0" 18 | }, 19 | "dependencies": { 20 | "@fermyon/spin-sdk": "0.6.0-rc.0", 21 | "node-html-parser": "^6.1.6" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /blog-recommendation-ts/runtime-config-template.toml: -------------------------------------------------------------------------------- 1 | # This tells Spin to use the remote host as its default database 2 | [sqlite_database.default] 3 | type = "libsql" 4 | url = "https://.turso.io" 5 | token = "" -------------------------------------------------------------------------------- /blog-recommendation-ts/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = "1" 2 | authors = ["karthik2804 "] 3 | description = "" 4 | name = "blog-recommendation" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "blog-recommendation" 10 | source = "target/blog-recommendation.wasm" 11 | exclude_files = ["**/node_modules"] 12 | key_value_stores = ["default"] 13 | sqlite_databases = ["default"] 14 | allowed_http_hosts = ["https://www.fermyon.com/"] 15 | ai_models = ["all-minilm-l6-v2"] 16 | [component.trigger] 17 | route = "/..." 18 | [component.build] 19 | command = "npm run build" 20 | -------------------------------------------------------------------------------- /blog-recommendation-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { EmbeddingModels, HandleRequest, HttpRequest, HttpResponse, Kv, Llm, Router, Sqlite } from "@fermyon/spin-sdk" 2 | import { checkCacheExpiry, fetchDataFromWebpage, send404response, sendResponse } from "./utils" 3 | 4 | // Base URL for fermyon blog 5 | const BASE_URL = "https://www.fermyon.com/blog/" 6 | 7 | interface EmbeddingRequest { 8 | blogPath: string 9 | } 10 | 11 | function listTable() { 12 | let db = Sqlite.openDefault() 13 | let url = db.execute("SELECT url FROM blog_posts", []) 14 | return sendResponse(200, {}, JSON.stringify(url)) 15 | } 16 | 17 | async function addEmbedding(request: HttpRequest) { 18 | let data = request.json() as EmbeddingRequest 19 | 20 | let db = Sqlite.openDefault() 21 | 22 | let { title, description } = await fetchDataFromWebpage(BASE_URL + data.blogPath) 23 | let embedding = Llm.generateEmbeddings(EmbeddingModels.AllMiniLmL6V2, [description]).embeddings[0] 24 | 25 | db.execute("INSERT INTO blog_posts (url, title, description, embedding) VALUES(?, ?, ?, ?) ON CONFLICT(url) DO UPDATE SET title=excluded.title,description=excluded.description,embedding=excluded.embedding", [data.blogPath, title, description, JSON.stringify(embedding)]) 26 | console.log("successfully inserted data") 27 | updateVirtualTable() 28 | 29 | return sendResponse(200, {}, `inserted embedding for ${data.blogPath}\n`) 30 | } 31 | 32 | async function getRecommendations(request: HttpRequest) { 33 | let store = Kv.openDefault() 34 | let data = request.json() as EmbeddingRequest 35 | if (store.exists(data.blogPath)) { 36 | let cache = store.getJson(data.blogPath) 37 | if(!checkCacheExpiry(Date.now(), cache.time)) { 38 | console.log("responding from cache") 39 | return sendResponse(200, {}, JSON.stringify(cache.content)) 40 | } 41 | } 42 | let db = Sqlite.openDefault() 43 | 44 | let res = db.execute("select description from blog_posts where url = ?", [data.blogPath]) 45 | let description = res.rows[0][0] as string 46 | let embedding = Llm.generateEmbeddings(EmbeddingModels.AllMiniLmL6V2, [description]).embeddings[0] 47 | let values = db.execute("select rowid from vss_blog_posts where vss_search(embedding, ?) limit 6;", [JSON.stringify(embedding)]) 48 | 49 | let index: number[] = [] 50 | values.rows.map((k) => { 51 | index.push(k[0] as number) 52 | 53 | }) 54 | index.shift() 55 | let result = db.execute("select url,title from blog_posts where rowid in (?,?,?,?,?)", [...index]) 56 | let posts: any = [] 57 | result.rows.map(k => { 58 | //@ts-ignore 59 | posts.push({blogPath: k[0], title: k[1]}) 60 | }) 61 | let cache = { 62 | time: Date.now(), 63 | content: posts 64 | } 65 | // cache responses for 5 minutes 66 | store.setJson(data.blogPath, cache) 67 | return sendResponse(200, {"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "*"}, JSON.stringify(posts, null, 2)) 68 | } 69 | 70 | function updateVirtualTable() { 71 | let db = Sqlite.openDefault() 72 | db.execute("DELETE FROM vss_blog_posts", []) 73 | db.execute("INSERT INTO vss_blog_posts(rowid,embedding) SELECT rowid,embedding FROM blog_posts;", []) 74 | console.log("updated virtual table entries successfully") 75 | } 76 | 77 | let router = Router() 78 | 79 | // used to list the content of the table 80 | router.get("/listTable", listTable) 81 | router.post("/addEmbedding", async (_, req) => { return await addEmbedding(req) }) 82 | // Get recommendation based on current article description 83 | router.post("/getRecommendations", async (_, req) => { return await getRecommendations(req) }) 84 | // Catch all 404 route 85 | router.all("*", () => { 86 | return send404response() 87 | }) 88 | 89 | export const handleRequest: HandleRequest = async function (request: HttpRequest): Promise { 90 | return await router.handleRequest(request, request) 91 | 92 | } 93 | 94 | -------------------------------------------------------------------------------- /blog-recommendation-ts/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { HttpResponse } from "@fermyon/spin-sdk" 2 | import { parse } from 'node-html-parser' 3 | 4 | interface WebpageData { 5 | title: string, 6 | description: string 7 | } 8 | 9 | async function fetchDataFromWebpage(url: string): Promise { 10 | let response = await fetch(url) 11 | let html = await response.text() 12 | let root = parse(html); 13 | let title = root.querySelector('h1')?.innerText || "" 14 | let description = root.querySelector('meta[name="description"]') 15 | return { 16 | title: unescapeHTML(title), 17 | description: description?.attributes.content || "" 18 | } 19 | } 20 | 21 | function sendResponse(status: number, headers?: Record, body?: string | ArrayBuffer): HttpResponse { 22 | return { 23 | status: status, 24 | headers: headers || {}, 25 | body: body || new Uint8Array() 26 | } 27 | } 28 | 29 | function send404response() { 30 | return { 31 | status: 404 32 | } 33 | } 34 | 35 | function unescapeHTML(str: string): string { 36 | return str.replace( 37 | /&|<|>|'|"|=|'/g, 38 | tag => 39 | ({ 40 | '&': '&', 41 | '<': '<', 42 | '>': '>', 43 | ''': "'", 44 | '"': '"', 45 | '=': '=', 46 | ''': "'" 47 | }[tag] || tag) 48 | ) 49 | } 50 | 51 | // 5 minute cache timer 52 | function checkCacheExpiry(currentTime: number, cachedTime: number) { 53 | return (currentTime - cachedTime) > 300000 54 | } 55 | 56 | export { fetchDataFromWebpage, sendResponse, send404response, checkCacheExpiry } -------------------------------------------------------------------------------- /blog-recommendation-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "module": "es6", 6 | "target": "es5", 7 | "jsx": "react", 8 | "skipLibCheck": true, 9 | "lib": ["ES2015"], 10 | "allowJs": true, 11 | "strict": true, 12 | "noImplicitReturns": true, 13 | "moduleResolution": "node" 14 | } 15 | } -------------------------------------------------------------------------------- /blog-recommendation-ts/update_embeddings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | file="blog_list.txt" 4 | 5 | while read -r post; do 6 | echo $post 7 | curl -X POST -d "{\"blogPath\": \"$post\"}" $1/addEmbedding 8 | done <$file -------------------------------------------------------------------------------- /blog-recommendation-ts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.tsx?$/, 9 | use: 'ts-loader', 10 | exclude: /node_modules/, 11 | }, 12 | ], 13 | }, 14 | resolve: { 15 | extensions: ['.tsx', '.ts', '.js'], 16 | }, 17 | output: { 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'spin.js', 20 | library: 'spin' 21 | }, 22 | optimization: { 23 | minimize: false 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /code-generator-rs/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .spin/ 3 | -------------------------------------------------------------------------------- /code-generator-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = ["api", "client"] 4 | -------------------------------------------------------------------------------- /code-generator-rs/README.md: -------------------------------------------------------------------------------- 1 | # Code Generator 2 | 3 | This sample uses the [Code Llama](https://ai.meta.com/blog/code-llama-large-language-model-coding/) model to help you write code. 4 | 5 | Please check the repo's [README](../README.md#prerequisites) for prerequisites for running this example. 6 | 7 | ## Build and Run the Spin Application 8 | 9 | ```bash 10 | $ cd api 11 | $ spin build --up 12 | ``` 13 | 14 | > Note: If you are using the Cloud GPU component, remember to reference the `runtime-config.toml` file, e.g.: `spin build --up --runtime-config-file ./runtime-config.toml`. 15 | 16 | ## Send requests 17 | 18 | You will be using a client application written in Rust to send requests to the Spin application. Run the following command from the root directory to send a request to the application. 19 | 20 | ```bash 21 | $ cargo r --bin client -- -l bash 'Find how large each directory named "target" is that is found in any subdirectory of my home directory' 22 | ``` 23 | 24 | ## Deploy the application to Fermyon Cloud 25 | 26 | ```bash 27 | $ cd api 28 | $ spin deploy 29 | ``` 30 | 31 | Make sure to change the url-reference in the `./client/src/main.rs`` file 32 | -------------------------------------------------------------------------------- /code-generator-rs/api/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.75" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.1.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "2.4.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 28 | 29 | [[package]] 30 | name = "bytes" 31 | version = "1.4.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 34 | 35 | [[package]] 36 | name = "code" 37 | version = "0.1.0" 38 | dependencies = [ 39 | "anyhow", 40 | "bytes", 41 | "http", 42 | "serde", 43 | "serde_json", 44 | "spin-sdk", 45 | ] 46 | 47 | [[package]] 48 | name = "fnv" 49 | version = "1.0.7" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 52 | 53 | [[package]] 54 | name = "form_urlencoded" 55 | version = "1.2.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 58 | dependencies = [ 59 | "percent-encoding", 60 | ] 61 | 62 | [[package]] 63 | name = "hashbrown" 64 | version = "0.12.3" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 67 | 68 | [[package]] 69 | name = "heck" 70 | version = "0.4.1" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 73 | dependencies = [ 74 | "unicode-segmentation", 75 | ] 76 | 77 | [[package]] 78 | name = "http" 79 | version = "0.2.9" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 82 | dependencies = [ 83 | "bytes", 84 | "fnv", 85 | "itoa", 86 | ] 87 | 88 | [[package]] 89 | name = "id-arena" 90 | version = "2.2.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 93 | 94 | [[package]] 95 | name = "idna" 96 | version = "0.4.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 99 | dependencies = [ 100 | "unicode-bidi", 101 | "unicode-normalization", 102 | ] 103 | 104 | [[package]] 105 | name = "indexmap" 106 | version = "1.9.3" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 109 | dependencies = [ 110 | "autocfg", 111 | "hashbrown", 112 | "serde", 113 | ] 114 | 115 | [[package]] 116 | name = "itoa" 117 | version = "1.0.9" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 120 | 121 | [[package]] 122 | name = "leb128" 123 | version = "0.2.5" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 126 | 127 | [[package]] 128 | name = "log" 129 | version = "0.4.20" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 132 | 133 | [[package]] 134 | name = "memchr" 135 | version = "2.6.2" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" 138 | 139 | [[package]] 140 | name = "percent-encoding" 141 | version = "2.3.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 144 | 145 | [[package]] 146 | name = "proc-macro2" 147 | version = "1.0.66" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 150 | dependencies = [ 151 | "unicode-ident", 152 | ] 153 | 154 | [[package]] 155 | name = "pulldown-cmark" 156 | version = "0.8.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" 159 | dependencies = [ 160 | "bitflags 1.3.2", 161 | "memchr", 162 | "unicase", 163 | ] 164 | 165 | [[package]] 166 | name = "quote" 167 | version = "1.0.33" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 170 | dependencies = [ 171 | "proc-macro2", 172 | ] 173 | 174 | [[package]] 175 | name = "routefinder" 176 | version = "0.5.3" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "94f8f99b10dedd317514253dda1fa7c14e344aac96e1f78149a64879ce282aca" 179 | dependencies = [ 180 | "smartcow", 181 | "smartstring", 182 | ] 183 | 184 | [[package]] 185 | name = "ryu" 186 | version = "1.0.15" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 189 | 190 | [[package]] 191 | name = "semver" 192 | version = "1.0.18" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" 195 | 196 | [[package]] 197 | name = "serde" 198 | version = "1.0.188" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 201 | dependencies = [ 202 | "serde_derive", 203 | ] 204 | 205 | [[package]] 206 | name = "serde_derive" 207 | version = "1.0.188" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 210 | dependencies = [ 211 | "proc-macro2", 212 | "quote", 213 | "syn 2.0.29", 214 | ] 215 | 216 | [[package]] 217 | name = "serde_json" 218 | version = "1.0.105" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" 221 | dependencies = [ 222 | "itoa", 223 | "ryu", 224 | "serde", 225 | ] 226 | 227 | [[package]] 228 | name = "smartcow" 229 | version = "0.2.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "656fcb1c1fca8c4655372134ce87d8afdf5ec5949ebabe8d314be0141d8b5da2" 232 | dependencies = [ 233 | "smartstring", 234 | ] 235 | 236 | [[package]] 237 | name = "smartstring" 238 | version = "1.0.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" 241 | dependencies = [ 242 | "autocfg", 243 | "static_assertions", 244 | "version_check", 245 | ] 246 | 247 | [[package]] 248 | name = "spin-macro" 249 | version = "0.1.0" 250 | source = "git+https://github.com/fermyon/spin?branch=llm-sdk#b918220f940691212d7e2758f2f4f765ecf1693a" 251 | dependencies = [ 252 | "anyhow", 253 | "bytes", 254 | "http", 255 | "proc-macro2", 256 | "quote", 257 | "syn 1.0.109", 258 | ] 259 | 260 | [[package]] 261 | name = "spin-sdk" 262 | version = "1.5.0-pre0" 263 | source = "git+https://github.com/fermyon/spin?branch=llm-sdk#b918220f940691212d7e2758f2f4f765ecf1693a" 264 | dependencies = [ 265 | "anyhow", 266 | "bytes", 267 | "form_urlencoded", 268 | "http", 269 | "routefinder", 270 | "spin-macro", 271 | "thiserror", 272 | "wit-bindgen", 273 | ] 274 | 275 | [[package]] 276 | name = "static_assertions" 277 | version = "1.1.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 280 | 281 | [[package]] 282 | name = "syn" 283 | version = "1.0.109" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 286 | dependencies = [ 287 | "proc-macro2", 288 | "quote", 289 | "unicode-ident", 290 | ] 291 | 292 | [[package]] 293 | name = "syn" 294 | version = "2.0.29" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" 297 | dependencies = [ 298 | "proc-macro2", 299 | "quote", 300 | "unicode-ident", 301 | ] 302 | 303 | [[package]] 304 | name = "thiserror" 305 | version = "1.0.47" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" 308 | dependencies = [ 309 | "thiserror-impl", 310 | ] 311 | 312 | [[package]] 313 | name = "thiserror-impl" 314 | version = "1.0.47" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" 317 | dependencies = [ 318 | "proc-macro2", 319 | "quote", 320 | "syn 2.0.29", 321 | ] 322 | 323 | [[package]] 324 | name = "tinyvec" 325 | version = "1.6.0" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 328 | dependencies = [ 329 | "tinyvec_macros", 330 | ] 331 | 332 | [[package]] 333 | name = "tinyvec_macros" 334 | version = "0.1.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 337 | 338 | [[package]] 339 | name = "unicase" 340 | version = "2.7.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 343 | dependencies = [ 344 | "version_check", 345 | ] 346 | 347 | [[package]] 348 | name = "unicode-bidi" 349 | version = "0.3.13" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 352 | 353 | [[package]] 354 | name = "unicode-ident" 355 | version = "1.0.11" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 358 | 359 | [[package]] 360 | name = "unicode-normalization" 361 | version = "0.1.22" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 364 | dependencies = [ 365 | "tinyvec", 366 | ] 367 | 368 | [[package]] 369 | name = "unicode-segmentation" 370 | version = "1.10.1" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 373 | 374 | [[package]] 375 | name = "unicode-xid" 376 | version = "0.2.4" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 379 | 380 | [[package]] 381 | name = "url" 382 | version = "2.4.1" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 385 | dependencies = [ 386 | "form_urlencoded", 387 | "idna", 388 | "percent-encoding", 389 | ] 390 | 391 | [[package]] 392 | name = "version_check" 393 | version = "0.9.4" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 396 | 397 | [[package]] 398 | name = "wasm-encoder" 399 | version = "0.29.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" 402 | dependencies = [ 403 | "leb128", 404 | ] 405 | 406 | [[package]] 407 | name = "wasm-metadata" 408 | version = "0.8.0" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "36e5156581ff4a302405c44ca7c85347563ca431d15f1a773f12c9c7b9a6cdc9" 411 | dependencies = [ 412 | "anyhow", 413 | "indexmap", 414 | "serde", 415 | "wasm-encoder", 416 | "wasmparser", 417 | ] 418 | 419 | [[package]] 420 | name = "wasmparser" 421 | version = "0.107.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" 424 | dependencies = [ 425 | "indexmap", 426 | "semver", 427 | ] 428 | 429 | [[package]] 430 | name = "wit-bindgen" 431 | version = "0.8.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "392d16e9e46cc7ca98125bc288dd5e4db469efe8323d3e0dac815ca7f2398522" 434 | dependencies = [ 435 | "bitflags 2.4.0", 436 | "wit-bindgen-rust-macro", 437 | ] 438 | 439 | [[package]] 440 | name = "wit-bindgen-core" 441 | version = "0.8.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "d422d36cbd78caa0e18c3371628447807c66ee72466b69865ea7e33682598158" 444 | dependencies = [ 445 | "anyhow", 446 | "wit-component", 447 | "wit-parser", 448 | ] 449 | 450 | [[package]] 451 | name = "wit-bindgen-rust" 452 | version = "0.8.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "9b76db68264f5d2089dc4652581236d8e75c5b89338de6187716215fd0e68ba3" 455 | dependencies = [ 456 | "heck", 457 | "wasm-metadata", 458 | "wit-bindgen-core", 459 | "wit-bindgen-rust-lib", 460 | "wit-component", 461 | ] 462 | 463 | [[package]] 464 | name = "wit-bindgen-rust-lib" 465 | version = "0.8.0" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "1c50f334bc08b0903a43387f6eea6ef6aa9eb2a085729f1677b29992ecef20ba" 468 | dependencies = [ 469 | "heck", 470 | "wit-bindgen-core", 471 | ] 472 | 473 | [[package]] 474 | name = "wit-bindgen-rust-macro" 475 | version = "0.8.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "ced38a5e174940c6a41ae587babeadfd2e2c2dc32f3b6488bcdca0e8922cf3f3" 478 | dependencies = [ 479 | "anyhow", 480 | "proc-macro2", 481 | "syn 2.0.29", 482 | "wit-bindgen-core", 483 | "wit-bindgen-rust", 484 | "wit-component", 485 | ] 486 | 487 | [[package]] 488 | name = "wit-component" 489 | version = "0.11.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "7cbd4c7f8f400327c482c88571f373844b7889e61460650d650fc5881bb3575c" 492 | dependencies = [ 493 | "anyhow", 494 | "bitflags 1.3.2", 495 | "indexmap", 496 | "log", 497 | "wasm-encoder", 498 | "wasm-metadata", 499 | "wasmparser", 500 | "wit-parser", 501 | ] 502 | 503 | [[package]] 504 | name = "wit-parser" 505 | version = "0.8.0" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "6daec9f093dbaea0e94043eeb92ece327bbbe70c86b1f41aca9bbfefd7f050f0" 508 | dependencies = [ 509 | "anyhow", 510 | "id-arena", 511 | "indexmap", 512 | "log", 513 | "pulldown-cmark", 514 | "semver", 515 | "unicode-xid", 516 | "url", 517 | ] 518 | -------------------------------------------------------------------------------- /code-generator-rs/api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "code" 3 | authors = ["Ryan Levick "] 4 | description = "" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | anyhow = "1" 13 | bytes = "1" 14 | http = "0.2" 15 | spin-sdk = { git = "https://github.com/fermyon/spin", tag = "v1.5.0" } 16 | serde = { version = "1.0", features = ["derive"] } 17 | serde_json = "1.0" 18 | -------------------------------------------------------------------------------- /code-generator-rs/api/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = "1" 2 | authors = ["Ryan Levick "] 3 | description = "" 4 | name = "code" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "code" 10 | source = "../target/wasm32-wasi/release/code.wasm" 11 | allowed_http_hosts = [] 12 | ai_models = ["codellama-instruct"] 13 | [component.trigger] 14 | route = "/..." 15 | [component.build] 16 | command = "cargo build --target wasm32-wasi --release" 17 | watch = ["src/**/*.rs", "Cargo.toml"] 18 | -------------------------------------------------------------------------------- /code-generator-rs/api/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use serde::Deserialize; 3 | use spin_sdk::{ 4 | http::{Request, Response}, 5 | http_component, llm, 6 | }; 7 | 8 | /// A simple Spin HTTP component. 9 | #[http_component] 10 | fn handle_code(req: Request) -> Result { 11 | let body = req 12 | .into_body() 13 | .and_then(|b| (!b.is_empty()).then_some(b)) 14 | .context("No body")?; 15 | let body: Body = serde_json::from_slice(&body) 16 | .map_err(|e| anyhow::anyhow!("could not deserialize body: {e}"))?; 17 | let prompt = gen_prompt(body.language, &body.prompt); 18 | 19 | let result = llm::infer_with_options( 20 | llm::InferencingModel::CodellamaInstruct, 21 | &prompt, 22 | llm::InferencingParams { 23 | max_tokens: 400, 24 | repeat_penalty: 1.1, 25 | repeat_penalty_last_n_token_count: 64, 26 | temperature: 0.8, 27 | top_k: 40, 28 | top_p: 0.9, 29 | }, 30 | )?; 31 | let response = clean_response(&result.text).to_owned().into(); 32 | 33 | Ok(http::Response::builder().status(200).body(Some(response))?) 34 | } 35 | 36 | #[derive(Deserialize, Clone, Default)] 37 | struct Body { 38 | prompt: String, 39 | language: Language, 40 | } 41 | 42 | #[derive(Deserialize, Clone, Default)] 43 | #[serde(rename_all = "snake_case")] 44 | enum Language { 45 | Python, 46 | #[default] 47 | Bash, 48 | } 49 | 50 | impl Language { 51 | fn name(&self) -> &str { 52 | match self { 53 | Language::Python => "python", 54 | Language::Bash => "bash", 55 | } 56 | } 57 | } 58 | 59 | const START_TAG: &str = "[CODE]"; 60 | const END_TAG: &str = "[/CODE]"; 61 | fn gen_prompt(language: Language, prompt: &str) -> String { 62 | let name = language.name(); 63 | format!("[INST] 64 | You are an expert {name} programmer. Write a full {name} script that does the following: {prompt}. 65 | Your answer should start with a {START_TAG} tag and end with a {END_TAG} tag. 66 | [/INST]") 67 | } 68 | 69 | fn clean_response<'a>(response: &'a str) -> &'a str { 70 | let mut response = response.trim_start().trim_end(); 71 | if let Some(i) = response.find(START_TAG) { 72 | response = &response[i + START_TAG.len()..] 73 | } 74 | if let Some(i) = response.rfind(END_TAG) { 75 | response = &response[..i] 76 | } 77 | response 78 | } 79 | -------------------------------------------------------------------------------- /code-generator-rs/client/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /code-generator-rs/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0" 8 | clap = { version = "4.4.2", features = ["derive"] } 9 | reqwest = { version = "0.11.20", features = ["blocking"] } 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0" 12 | -------------------------------------------------------------------------------- /code-generator-rs/client/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, ValueEnum}; 2 | use reqwest::blocking as reqwest; 3 | use serde::Serialize; 4 | 5 | #[derive(Parser)] 6 | #[command(author, version, about, long_about = None)] 7 | struct Cli { 8 | #[arg(short, long, value_enum)] 9 | /// Language for the task 10 | language: Language, 11 | /// Task to perform 12 | prompt: String, 13 | } 14 | 15 | #[derive(Parser, Clone, ValueEnum, Default, Serialize)] 16 | #[serde(rename_all = "snake_case")] 17 | enum Language { 18 | Python, 19 | #[default] 20 | Bash, 21 | } 22 | 23 | #[derive(Clone, Serialize)] 24 | struct Body { 25 | language: Language, 26 | prompt: String, 27 | } 28 | 29 | fn main() -> anyhow::Result<()> { 30 | let cli = Cli::parse(); 31 | let response = reqwest::Client::new() 32 | .post("http://localhost:3000") 33 | .body(serde_json::to_vec(&Body { 34 | language: cli.language, 35 | prompt: cli.prompt, 36 | })?) 37 | .send()?; 38 | let response = response.bytes()?; 39 | let response = std::str::from_utf8(&response)?; 40 | println!("{}", response); 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /haiku-generator-assets/dynamic.js: -------------------------------------------------------------------------------- 1 | // Listen for the Enter key being pressed 2 | document.addEventListener("keydown", function (event) { 3 | if (event.keyCode === 13) { 4 | newCard(); 5 | } 6 | }); 7 | 8 | var globalCardCount = 0; 9 | var runningInference = false; 10 | 11 | function newCard() { 12 | if (runningInference) { 13 | console.log("Already running inference, please wait..."); 14 | setAlert("Already running inference, please wait..."); 15 | return; 16 | } 17 | var inputElement = document.getElementById("sentence-input"); 18 | var sentence = inputElement.value; 19 | if (sentence === "") { 20 | console.log("Please enter a sentence to analyze"); 21 | setAlert("Please enter a sentence to analyze"); 22 | return; 23 | } 24 | inputElement.value = ""; 25 | 26 | var cardIndex = globalCardCount; 27 | globalCardCount++; 28 | var newCard = document.createElement("div"); 29 | newCard.id = "card-" + cardIndex; 30 | newCard.innerHTML = ` 31 |
32 |
33 |
${sentence}
34 |
35 | 36 |
37 |
38 |
39 | `; 40 | document.getElementById("sentence-input").before(newCard); 41 | 42 | console.log("Running inference on sentence: " + sentence); 43 | runningInference = true; 44 | fetch("/api/haiku-writing", { 45 | method: "POST", 46 | headers: { 47 | "Content-Type": "application/json", 48 | }, 49 | body: JSON.stringify({ sentence: sentence }), 50 | }) 51 | .then((response) => response.json()) 52 | .then((data) => { 53 | updateCard(cardIndex, sentence, data.sentiment); 54 | }) 55 | .catch((error) => { 56 | console.log(error); 57 | }); 58 | } 59 | 60 | function updateCard(cardIndex, sentence, sentiment) { 61 | badge = ""; 62 | badge = `sentiment`; 63 | 64 | var cardElement = document.getElementById("card-" + cardIndex); 65 | cardElement.innerHTML = ` 66 |
67 |
68 |
${sentence}
69 |
70 | ${badge} 71 |
72 |
73 |
74 | `; 75 | runningInference = false; 76 | } 77 | 78 | function setAlert(msg) { 79 | var alertElement = document.getElementById("alert"); 80 | alertElement.innerHTML = ` 81 |
82 | 83 | ${msg} 84 |
85 | `; 86 | setTimeout(function () { 87 | alertElement.innerHTML = ""; 88 | }, 3000); 89 | } 90 | -------------------------------------------------------------------------------- /haiku-generator-assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Haiku Generator 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
25 | 35 |
36 |
37 |

38 | This Haiku generator is a demonstration of how you can use Fermyon 39 | Serverless AI to easily make an AI-powered API. When you type in a 40 | topic it is sent to a Spin app running in the Fermyon Cloud and a haiku 41 | is generated with a pre-trained model. 42 |

43 |

44 | Note that LLM's are not perfect and the haikus performed 45 | by this application is not guaranteed to perfectly follow the format of 5-7-5. 46 |

47 |

48 | To get started type a topic below and press 49 | enter. 50 |

51 | 52 |
53 | 59 |
60 | 61 |
62 |
63 |
64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /haiku-generator-rs/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .spin/ 3 | -------------------------------------------------------------------------------- /haiku-generator-rs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.75" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.1.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "2.4.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 28 | 29 | [[package]] 30 | name = "bytes" 31 | version = "1.5.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 34 | 35 | [[package]] 36 | name = "fnv" 37 | version = "1.0.7" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 40 | 41 | [[package]] 42 | name = "form_urlencoded" 43 | version = "1.2.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 46 | dependencies = [ 47 | "percent-encoding", 48 | ] 49 | 50 | [[package]] 51 | name = "haiku-generator-rs" 52 | version = "0.1.0" 53 | dependencies = [ 54 | "anyhow", 55 | "bytes", 56 | "http", 57 | "serde", 58 | "serde_json", 59 | "spin-sdk", 60 | ] 61 | 62 | [[package]] 63 | name = "hashbrown" 64 | version = "0.12.3" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 67 | 68 | [[package]] 69 | name = "heck" 70 | version = "0.4.1" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 73 | dependencies = [ 74 | "unicode-segmentation", 75 | ] 76 | 77 | [[package]] 78 | name = "http" 79 | version = "0.2.9" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 82 | dependencies = [ 83 | "bytes", 84 | "fnv", 85 | "itoa", 86 | ] 87 | 88 | [[package]] 89 | name = "id-arena" 90 | version = "2.2.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 93 | 94 | [[package]] 95 | name = "idna" 96 | version = "0.4.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 99 | dependencies = [ 100 | "unicode-bidi", 101 | "unicode-normalization", 102 | ] 103 | 104 | [[package]] 105 | name = "indexmap" 106 | version = "1.9.3" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 109 | dependencies = [ 110 | "autocfg", 111 | "hashbrown", 112 | "serde", 113 | ] 114 | 115 | [[package]] 116 | name = "itoa" 117 | version = "1.0.9" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 120 | 121 | [[package]] 122 | name = "leb128" 123 | version = "0.2.5" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 126 | 127 | [[package]] 128 | name = "log" 129 | version = "0.4.20" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 132 | 133 | [[package]] 134 | name = "memchr" 135 | version = "2.6.3" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 138 | 139 | [[package]] 140 | name = "percent-encoding" 141 | version = "2.3.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 144 | 145 | [[package]] 146 | name = "proc-macro2" 147 | version = "1.0.67" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" 150 | dependencies = [ 151 | "unicode-ident", 152 | ] 153 | 154 | [[package]] 155 | name = "pulldown-cmark" 156 | version = "0.8.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" 159 | dependencies = [ 160 | "bitflags 1.3.2", 161 | "memchr", 162 | "unicase", 163 | ] 164 | 165 | [[package]] 166 | name = "quote" 167 | version = "1.0.33" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 170 | dependencies = [ 171 | "proc-macro2", 172 | ] 173 | 174 | [[package]] 175 | name = "routefinder" 176 | version = "0.5.3" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "94f8f99b10dedd317514253dda1fa7c14e344aac96e1f78149a64879ce282aca" 179 | dependencies = [ 180 | "smartcow", 181 | "smartstring", 182 | ] 183 | 184 | [[package]] 185 | name = "ryu" 186 | version = "1.0.15" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 189 | 190 | [[package]] 191 | name = "semver" 192 | version = "1.0.18" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" 195 | 196 | [[package]] 197 | name = "serde" 198 | version = "1.0.188" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 201 | dependencies = [ 202 | "serde_derive", 203 | ] 204 | 205 | [[package]] 206 | name = "serde_derive" 207 | version = "1.0.188" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 210 | dependencies = [ 211 | "proc-macro2", 212 | "quote", 213 | "syn 2.0.37", 214 | ] 215 | 216 | [[package]] 217 | name = "serde_json" 218 | version = "1.0.107" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 221 | dependencies = [ 222 | "itoa", 223 | "ryu", 224 | "serde", 225 | ] 226 | 227 | [[package]] 228 | name = "smartcow" 229 | version = "0.2.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "656fcb1c1fca8c4655372134ce87d8afdf5ec5949ebabe8d314be0141d8b5da2" 232 | dependencies = [ 233 | "smartstring", 234 | ] 235 | 236 | [[package]] 237 | name = "smartstring" 238 | version = "1.0.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" 241 | dependencies = [ 242 | "autocfg", 243 | "static_assertions", 244 | "version_check", 245 | ] 246 | 247 | [[package]] 248 | name = "spin-macro" 249 | version = "0.1.0" 250 | source = "git+https://github.com/fermyon/spin?tag=v1.5.0#ca08dd933de32fe09f4c318fbbc1f04853f2085f" 251 | dependencies = [ 252 | "anyhow", 253 | "bytes", 254 | "http", 255 | "proc-macro2", 256 | "quote", 257 | "syn 1.0.109", 258 | ] 259 | 260 | [[package]] 261 | name = "spin-sdk" 262 | version = "1.5.0" 263 | source = "git+https://github.com/fermyon/spin?tag=v1.5.0#ca08dd933de32fe09f4c318fbbc1f04853f2085f" 264 | dependencies = [ 265 | "anyhow", 266 | "bytes", 267 | "form_urlencoded", 268 | "http", 269 | "routefinder", 270 | "spin-macro", 271 | "thiserror", 272 | "wit-bindgen", 273 | ] 274 | 275 | [[package]] 276 | name = "static_assertions" 277 | version = "1.1.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 280 | 281 | [[package]] 282 | name = "syn" 283 | version = "1.0.109" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 286 | dependencies = [ 287 | "proc-macro2", 288 | "quote", 289 | "unicode-ident", 290 | ] 291 | 292 | [[package]] 293 | name = "syn" 294 | version = "2.0.37" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" 297 | dependencies = [ 298 | "proc-macro2", 299 | "quote", 300 | "unicode-ident", 301 | ] 302 | 303 | [[package]] 304 | name = "thiserror" 305 | version = "1.0.48" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" 308 | dependencies = [ 309 | "thiserror-impl", 310 | ] 311 | 312 | [[package]] 313 | name = "thiserror-impl" 314 | version = "1.0.48" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" 317 | dependencies = [ 318 | "proc-macro2", 319 | "quote", 320 | "syn 2.0.37", 321 | ] 322 | 323 | [[package]] 324 | name = "tinyvec" 325 | version = "1.6.0" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 328 | dependencies = [ 329 | "tinyvec_macros", 330 | ] 331 | 332 | [[package]] 333 | name = "tinyvec_macros" 334 | version = "0.1.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 337 | 338 | [[package]] 339 | name = "unicase" 340 | version = "2.7.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 343 | dependencies = [ 344 | "version_check", 345 | ] 346 | 347 | [[package]] 348 | name = "unicode-bidi" 349 | version = "0.3.13" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 352 | 353 | [[package]] 354 | name = "unicode-ident" 355 | version = "1.0.12" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 358 | 359 | [[package]] 360 | name = "unicode-normalization" 361 | version = "0.1.22" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 364 | dependencies = [ 365 | "tinyvec", 366 | ] 367 | 368 | [[package]] 369 | name = "unicode-segmentation" 370 | version = "1.10.1" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 373 | 374 | [[package]] 375 | name = "unicode-xid" 376 | version = "0.2.4" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 379 | 380 | [[package]] 381 | name = "url" 382 | version = "2.4.1" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 385 | dependencies = [ 386 | "form_urlencoded", 387 | "idna", 388 | "percent-encoding", 389 | ] 390 | 391 | [[package]] 392 | name = "version_check" 393 | version = "0.9.4" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 396 | 397 | [[package]] 398 | name = "wasm-encoder" 399 | version = "0.29.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" 402 | dependencies = [ 403 | "leb128", 404 | ] 405 | 406 | [[package]] 407 | name = "wasm-metadata" 408 | version = "0.8.0" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "36e5156581ff4a302405c44ca7c85347563ca431d15f1a773f12c9c7b9a6cdc9" 411 | dependencies = [ 412 | "anyhow", 413 | "indexmap", 414 | "serde", 415 | "wasm-encoder", 416 | "wasmparser", 417 | ] 418 | 419 | [[package]] 420 | name = "wasmparser" 421 | version = "0.107.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" 424 | dependencies = [ 425 | "indexmap", 426 | "semver", 427 | ] 428 | 429 | [[package]] 430 | name = "wit-bindgen" 431 | version = "0.8.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "392d16e9e46cc7ca98125bc288dd5e4db469efe8323d3e0dac815ca7f2398522" 434 | dependencies = [ 435 | "bitflags 2.4.0", 436 | "wit-bindgen-rust-macro", 437 | ] 438 | 439 | [[package]] 440 | name = "wit-bindgen-core" 441 | version = "0.8.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "d422d36cbd78caa0e18c3371628447807c66ee72466b69865ea7e33682598158" 444 | dependencies = [ 445 | "anyhow", 446 | "wit-component", 447 | "wit-parser", 448 | ] 449 | 450 | [[package]] 451 | name = "wit-bindgen-rust" 452 | version = "0.8.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "9b76db68264f5d2089dc4652581236d8e75c5b89338de6187716215fd0e68ba3" 455 | dependencies = [ 456 | "heck", 457 | "wasm-metadata", 458 | "wit-bindgen-core", 459 | "wit-bindgen-rust-lib", 460 | "wit-component", 461 | ] 462 | 463 | [[package]] 464 | name = "wit-bindgen-rust-lib" 465 | version = "0.8.0" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "1c50f334bc08b0903a43387f6eea6ef6aa9eb2a085729f1677b29992ecef20ba" 468 | dependencies = [ 469 | "heck", 470 | "wit-bindgen-core", 471 | ] 472 | 473 | [[package]] 474 | name = "wit-bindgen-rust-macro" 475 | version = "0.8.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "ced38a5e174940c6a41ae587babeadfd2e2c2dc32f3b6488bcdca0e8922cf3f3" 478 | dependencies = [ 479 | "anyhow", 480 | "proc-macro2", 481 | "syn 2.0.37", 482 | "wit-bindgen-core", 483 | "wit-bindgen-rust", 484 | "wit-component", 485 | ] 486 | 487 | [[package]] 488 | name = "wit-component" 489 | version = "0.11.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "7cbd4c7f8f400327c482c88571f373844b7889e61460650d650fc5881bb3575c" 492 | dependencies = [ 493 | "anyhow", 494 | "bitflags 1.3.2", 495 | "indexmap", 496 | "log", 497 | "wasm-encoder", 498 | "wasm-metadata", 499 | "wasmparser", 500 | "wit-parser", 501 | ] 502 | 503 | [[package]] 504 | name = "wit-parser" 505 | version = "0.8.0" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "6daec9f093dbaea0e94043eeb92ece327bbbe70c86b1f41aca9bbfefd7f050f0" 508 | dependencies = [ 509 | "anyhow", 510 | "id-arena", 511 | "indexmap", 512 | "log", 513 | "pulldown-cmark", 514 | "semver", 515 | "unicode-xid", 516 | "url", 517 | ] 518 | -------------------------------------------------------------------------------- /haiku-generator-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "haiku-generator-rs" 3 | authors = ["tpmccallum "] 4 | description = "An application that can write haiku poetry via Fermyon Serverless AI" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = [ "cdylib" ] 10 | 11 | [dependencies] 12 | serde = { version = "1.0", features = ["derive"] } 13 | serde_json = "1.0.85" 14 | # Useful crate to handle errors. 15 | anyhow = "1" 16 | # Crate to simplify working with bytes. 17 | bytes = "1" 18 | # General-purpose crate with common HTTP types. 19 | http = "0.2" 20 | # The Spin SDK. 21 | spin-sdk = { git = "https://github.com/fermyon/spin", tag = "v1.5.0" } 22 | 23 | [workspace] 24 | -------------------------------------------------------------------------------- /haiku-generator-rs/README.md: -------------------------------------------------------------------------------- 1 | # Haiku Generator 2 | 3 | This repository contains a Spin application that generates a haiku in the 5-7-5 format and a simple UI to interact with it. 4 | 5 | Please check the repo's [README](../README.md#prerequisites) for prerequisites for running this example. 6 | 7 | ## Build and Run the Application 8 | 9 | ```bash 10 | $ cd ../../../haiku-generator-rs 11 | $ spin build --up 12 | ``` 13 | 14 | > Note: If you are using the Cloud GPU component, remember to reference the `runtime-config.toml` file, e.g.: `spin build --up --runtime-config-file ./runtime-config.toml`. 15 | 16 | ## Test the Application in the CLI 17 | 18 | ```bash 19 | $ curl --json '{"sentence": "Please write a haiku about ChatGPT and Grammarly and AI for me now."}' http://localhost:3000/api/haiku-writing 20 | ``` 21 | 22 | ## Deploy the application to Fermyon Cloud 23 | 24 | ```bash 25 | $ spin deploy 26 | ``` 27 | -------------------------------------------------------------------------------- /haiku-generator-rs/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = "1" 2 | authors = ["tpmccallum "] 3 | description = "An application that can write haiku poetry via Fermyon Serverless AI" 4 | name = "haiku-generator-rs" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "haiku-generator-rs" 10 | source = "target/wasm32-wasi/release/haiku_generator_rs.wasm" 11 | allowed_http_hosts = [] 12 | ai_models = ["llama2-chat"] 13 | key_value_stores = ["default"] 14 | [component.trigger] 15 | route = "/..." 16 | [component.build] 17 | command = "cargo build --target wasm32-wasi --release" 18 | watch = ["src/**/*.rs", "Cargo.toml"] 19 | -------------------------------------------------------------------------------- /haiku-generator-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use spin_sdk::{ 3 | http::{Params, Request, Response, Router}, 4 | http_component, 5 | llm::{infer, InferencingModel::Llama2Chat}, 6 | }; 7 | 8 | use serde::{Deserialize, Serialize}; 9 | 10 | #[derive(Deserialize)] 11 | pub struct HaikuRequest { 12 | pub sentence: String, 13 | } 14 | 15 | #[derive(Serialize)] 16 | pub struct HaikuResponse { 17 | pub sentiment: String, 18 | } 19 | 20 | //const PROMPT: &str = r#"[INST]<>You are a haiku poetry assistant. You will create a Haiku about a given topic using graphemes.The hauki poetry you write for me, will always have 5 syllables in the first line, 7 syllables in the second line and 5 syllables in the 3rd (and final) line.<>Please write me a haiku about {SENTENCE} now.[/INST]"#; 21 | const PROMPT: &str = r#"[INST] <> You will create a Haiku about a given topic using graphemes. <> {SENTENCE} [/INST]"#; 22 | /// A Spin HTTP component that internally routes requests. 23 | #[http_component] 24 | fn handle_route(req: Request) -> Result { 25 | let mut router = Router::new(); 26 | router.post("/api/haiku-writing", perform_haiku_writing); 27 | router.any("/api/*", not_found); 28 | router.handle(req) 29 | } 30 | 31 | fn not_found(_: Request, _: Params) -> Result { 32 | Ok(http::Response::builder() 33 | .status(404) 34 | .body(Some("Not found".into()))?) 35 | } 36 | 37 | fn perform_haiku_writing(req: Request, _params: Params) -> Result { 38 | let request = body_json_to_map(&req)?; 39 | // Do some basic clean up on the input 40 | let sentence = request.sentence.trim(); 41 | println!("Writing a haiku on the following topic: {}", sentence); 42 | 43 | println!("Running inference"); 44 | let inferencing_result = infer(Llama2Chat, &PROMPT.replace("{SENTENCE}", sentence))?; 45 | println!("Inference result {:?}", inferencing_result.text); 46 | send_ok_response(200, inferencing_result.text) 47 | } 48 | 49 | fn send_ok_response(code: u16, resp_str: String) -> Result { 50 | Ok(http::Response::builder() 51 | .status(code) 52 | .body(Some(resp_str.into()))?) 53 | } 54 | 55 | fn body_json_to_map(req: &Request) -> Result { 56 | let body = match req.body().as_ref() { 57 | Some(bytes) => bytes, 58 | None => anyhow::bail!("Request body was unexpectedly empty"), 59 | }; 60 | 61 | Ok(serde_json::from_slice(&body)?) 62 | } 63 | -------------------------------------------------------------------------------- /haiku-generator-ts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | target 4 | .spin/ -------------------------------------------------------------------------------- /haiku-generator-ts/README.md: -------------------------------------------------------------------------------- 1 | # Haiku Generator 2 | 3 | This repository contains a Spin application that generates a haiku in the 5-7-5 format and a simple UI to interact with it. 4 | 5 | Please check the repo's [README](../README.md#prerequisites) for prerequisites for running this example. 6 | 7 | ## Build and Running 8 | 9 | ```bash 10 | $ npm install 11 | $ spin build --up 12 | ``` 13 | 14 | > Note: If you are using the Cloud GPU component, remember to reference the `runtime-config.toml` file, e.g.: `spin build --up --runtime-config-file ./runtime-config.toml`. 15 | 16 | You can access the UI at http://localhost:3000 and use the web interface or you can use the following curl command: 17 | 18 | ```bash 19 | curl -X POST http://localhost:3000/api/haiku-writing -H 'Content-Type: application/json' -d '{"sentence":"ChatGPT"}' 20 | ``` 21 | 22 | ## Deploy the application to Fermyon Cloud 23 | 24 | ```bash 25 | $ cd api 26 | $ spin deploy 27 | ``` 28 | -------------------------------------------------------------------------------- /haiku-generator-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "haiku-generator-ts", 3 | "version": "1.0.0", 4 | "description": "An application that can write haiku poetry via Fermyon Serverless AI", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npx webpack --mode=production && mkdir -p target && spin js2wasm -o target/haiku-generator-ts.wasm dist/spin.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "ts-loader": "^9.4.4", 15 | "webpack": "^5.74.0", 16 | "webpack-cli": "^4.10.0" 17 | }, 18 | "dependencies": { 19 | "@fermyon/spin-sdk": "^0.6.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /haiku-generator-ts/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = "1" 2 | authors = ["tpmccallum "] 3 | description = "An application that can write haiku poetry via Fermyon Serverless AI" 4 | name = "haiku-generator-ts" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "haiku-generator-ts" 10 | source = "target/haiku-generator-ts.wasm" 11 | exclude_files = ["**/node_modules"] 12 | ai_models = ["llama2-chat"] 13 | [component.trigger] 14 | route = "/api/..." 15 | [component.build] 16 | command = "npm run build" 17 | watch = ["src/**/*", "package.json", "package-lock.json"] 18 | 19 | [[component]] 20 | source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.0.1/spin_static_fs.wasm", digest = "sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375" } 21 | id = "ui" 22 | files = [{ source = "../haiku-generator-assets", destination = "/" }] 23 | [component.trigger] 24 | route = "/..." -------------------------------------------------------------------------------- /haiku-generator-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HandleRequest, 3 | HttpRequest, 4 | HttpResponse, 5 | Llm, 6 | InferencingModels, 7 | InferencingOptions, 8 | Router, 9 | } from "@fermyon/spin-sdk"; 10 | 11 | interface HaikuRequest { 12 | sentence: string; 13 | } 14 | 15 | interface HaikuResponse { 16 | sentiment: string; 17 | } 18 | 19 | const PROMPT = `\ 20 | [INST] 21 | <> 22 | You will create a Haiku about a given topic using graphemes. 23 | <> 24 | User: {SENTENCE} 25 | [/INST] 26 | `; 27 | 28 | async function performHaikuGenerator(request: HttpRequest) { 29 | // Parse sentence out of request 30 | let data = request.json() as HaikuRequest; 31 | let sentence = data.sentence.trim(); 32 | console.log("Generating haiku on: " + sentence); 33 | console.log("Running inference"); 34 | let inferenceResult = Llm.infer( 35 | InferencingModels.Llama2Chat, 36 | PROMPT.replace("{SENTENCE}", sentence) 37 | ); 38 | console.log( 39 | `Inference result (${inferenceResult.usage.generatedTokenCount} tokens): ${inferenceResult.text}` 40 | ); 41 | 42 | 43 | return { 44 | status: 200, 45 | body: inferenceResult.text }; 46 | } 47 | 48 | let router = Router(); 49 | 50 | // Map the route to the handler 51 | router.post("/api/haiku-writing", async (_, req) => { 52 | console.log(`${new Date().toISOString()} POST /haiku-writing`); 53 | return await performHaikuGenerator(req); 54 | }); 55 | 56 | // Catch all 404 handler 57 | router.all("/api/*", async (_, _req) => { 58 | return { 59 | status: 404, 60 | body: "Not found", 61 | }; 62 | }); 63 | 64 | // Entry point to the Spin handler 65 | export const handleRequest: HandleRequest = async function( 66 | request: HttpRequest 67 | ): Promise { 68 | return await router.handleRequest(request, request); 69 | }; 70 | -------------------------------------------------------------------------------- /haiku-generator-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "module": "es6", 6 | "target": "es5", 7 | "jsx": "react", 8 | "skipLibCheck": true, 9 | "lib": ["ES2015"], 10 | "allowJs": true, 11 | "strict": true, 12 | "noImplicitReturns": true, 13 | "moduleResolution": "node" 14 | } 15 | } -------------------------------------------------------------------------------- /haiku-generator-ts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.tsx?$/, 9 | use: 'ts-loader', 10 | exclude: /node_modules/, 11 | }, 12 | ], 13 | }, 14 | resolve: { 15 | extensions: ['.tsx', '.ts', '.js'], 16 | }, 17 | output: { 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'spin.js', 20 | library: 'spin' 21 | }, 22 | optimization: { 23 | minimize: false 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /helloworld-py/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.wasm 3 | .spin -------------------------------------------------------------------------------- /helloworld-py/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | 8 | [dev-packages] 9 | 10 | [requires] 11 | python_version = "3.10" 12 | -------------------------------------------------------------------------------- /helloworld-py/README.md: -------------------------------------------------------------------------------- 1 | # A simple Spin HTTP component in Python -------------------------------------------------------------------------------- /helloworld-py/app.py: -------------------------------------------------------------------------------- 1 | from spin_http import Response 2 | from spin_llm import llm_infer 3 | import json 4 | import re 5 | 6 | def handle_request(request): 7 | try: 8 | result = llm_infer("llama2-chat", "Can you tell me a joke abut cats") 9 | return Response(200, {"content-type": "text/plain"}, bytes(result.text, "utf-8")) 10 | except Exception as e: 11 | return Response(500, {"content-type": "text/plain"}, bytes(f"Error: {str(e)}", "utf-8")) 12 | 13 | -------------------------------------------------------------------------------- /helloworld-py/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = "1" 2 | authors = ["Sohan <1119120+sohanmaheshwar@users.noreply.github.com>"] 3 | description = "A Hello World Serverless AI application written in Python" 4 | name = "helloworld-py" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "helloworld-py" 10 | source = "app.wasm" 11 | ai_models = ["llama2-chat"] 12 | [component.trigger] 13 | route = "/..." 14 | [component.build] 15 | command = "spin py2wasm app -o app.wasm" 16 | watch = ["app.py", "Pipfile"] 17 | -------------------------------------------------------------------------------- /helloworld-rs/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .spin/ 3 | -------------------------------------------------------------------------------- /helloworld-rs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.75" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.1.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "2.4.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 28 | 29 | [[package]] 30 | name = "bytes" 31 | version = "1.5.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 34 | 35 | [[package]] 36 | name = "fnv" 37 | version = "1.0.7" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 40 | 41 | [[package]] 42 | name = "form_urlencoded" 43 | version = "1.2.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 46 | dependencies = [ 47 | "percent-encoding", 48 | ] 49 | 50 | [[package]] 51 | name = "hashbrown" 52 | version = "0.12.3" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 55 | 56 | [[package]] 57 | name = "heck" 58 | version = "0.4.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 61 | dependencies = [ 62 | "unicode-segmentation", 63 | ] 64 | 65 | [[package]] 66 | name = "helloworld-rs" 67 | version = "0.1.0" 68 | dependencies = [ 69 | "anyhow", 70 | "bytes", 71 | "http", 72 | "spin-sdk", 73 | ] 74 | 75 | [[package]] 76 | name = "http" 77 | version = "0.2.9" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 80 | dependencies = [ 81 | "bytes", 82 | "fnv", 83 | "itoa", 84 | ] 85 | 86 | [[package]] 87 | name = "id-arena" 88 | version = "2.2.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 91 | 92 | [[package]] 93 | name = "idna" 94 | version = "0.4.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 97 | dependencies = [ 98 | "unicode-bidi", 99 | "unicode-normalization", 100 | ] 101 | 102 | [[package]] 103 | name = "indexmap" 104 | version = "1.9.3" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 107 | dependencies = [ 108 | "autocfg", 109 | "hashbrown", 110 | "serde", 111 | ] 112 | 113 | [[package]] 114 | name = "itoa" 115 | version = "1.0.9" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 118 | 119 | [[package]] 120 | name = "leb128" 121 | version = "0.2.5" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 124 | 125 | [[package]] 126 | name = "log" 127 | version = "0.4.20" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 130 | 131 | [[package]] 132 | name = "memchr" 133 | version = "2.6.4" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 136 | 137 | [[package]] 138 | name = "percent-encoding" 139 | version = "2.3.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 142 | 143 | [[package]] 144 | name = "proc-macro2" 145 | version = "1.0.69" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" 148 | dependencies = [ 149 | "unicode-ident", 150 | ] 151 | 152 | [[package]] 153 | name = "pulldown-cmark" 154 | version = "0.8.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" 157 | dependencies = [ 158 | "bitflags 1.3.2", 159 | "memchr", 160 | "unicase", 161 | ] 162 | 163 | [[package]] 164 | name = "quote" 165 | version = "1.0.33" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 168 | dependencies = [ 169 | "proc-macro2", 170 | ] 171 | 172 | [[package]] 173 | name = "routefinder" 174 | version = "0.5.3" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "94f8f99b10dedd317514253dda1fa7c14e344aac96e1f78149a64879ce282aca" 177 | dependencies = [ 178 | "smartcow", 179 | "smartstring", 180 | ] 181 | 182 | [[package]] 183 | name = "semver" 184 | version = "1.0.20" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" 187 | 188 | [[package]] 189 | name = "serde" 190 | version = "1.0.189" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" 193 | dependencies = [ 194 | "serde_derive", 195 | ] 196 | 197 | [[package]] 198 | name = "serde_derive" 199 | version = "1.0.189" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" 202 | dependencies = [ 203 | "proc-macro2", 204 | "quote", 205 | "syn 2.0.38", 206 | ] 207 | 208 | [[package]] 209 | name = "smartcow" 210 | version = "0.2.1" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "656fcb1c1fca8c4655372134ce87d8afdf5ec5949ebabe8d314be0141d8b5da2" 213 | dependencies = [ 214 | "smartstring", 215 | ] 216 | 217 | [[package]] 218 | name = "smartstring" 219 | version = "1.0.1" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" 222 | dependencies = [ 223 | "autocfg", 224 | "static_assertions", 225 | "version_check", 226 | ] 227 | 228 | [[package]] 229 | name = "spin-macro" 230 | version = "0.1.0" 231 | source = "git+https://github.com/fermyon/spin?tag=v1.5.1#8d4334eca6eb2b9cec0105612cc9a38402f839b4" 232 | dependencies = [ 233 | "anyhow", 234 | "bytes", 235 | "http", 236 | "proc-macro2", 237 | "quote", 238 | "syn 1.0.109", 239 | ] 240 | 241 | [[package]] 242 | name = "spin-sdk" 243 | version = "1.5.1" 244 | source = "git+https://github.com/fermyon/spin?tag=v1.5.1#8d4334eca6eb2b9cec0105612cc9a38402f839b4" 245 | dependencies = [ 246 | "anyhow", 247 | "bytes", 248 | "form_urlencoded", 249 | "http", 250 | "routefinder", 251 | "spin-macro", 252 | "thiserror", 253 | "wit-bindgen", 254 | ] 255 | 256 | [[package]] 257 | name = "static_assertions" 258 | version = "1.1.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 261 | 262 | [[package]] 263 | name = "syn" 264 | version = "1.0.109" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 267 | dependencies = [ 268 | "proc-macro2", 269 | "quote", 270 | "unicode-ident", 271 | ] 272 | 273 | [[package]] 274 | name = "syn" 275 | version = "2.0.38" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" 278 | dependencies = [ 279 | "proc-macro2", 280 | "quote", 281 | "unicode-ident", 282 | ] 283 | 284 | [[package]] 285 | name = "thiserror" 286 | version = "1.0.50" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" 289 | dependencies = [ 290 | "thiserror-impl", 291 | ] 292 | 293 | [[package]] 294 | name = "thiserror-impl" 295 | version = "1.0.50" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" 298 | dependencies = [ 299 | "proc-macro2", 300 | "quote", 301 | "syn 2.0.38", 302 | ] 303 | 304 | [[package]] 305 | name = "tinyvec" 306 | version = "1.6.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 309 | dependencies = [ 310 | "tinyvec_macros", 311 | ] 312 | 313 | [[package]] 314 | name = "tinyvec_macros" 315 | version = "0.1.1" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 318 | 319 | [[package]] 320 | name = "unicase" 321 | version = "2.7.0" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 324 | dependencies = [ 325 | "version_check", 326 | ] 327 | 328 | [[package]] 329 | name = "unicode-bidi" 330 | version = "0.3.13" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 333 | 334 | [[package]] 335 | name = "unicode-ident" 336 | version = "1.0.12" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 339 | 340 | [[package]] 341 | name = "unicode-normalization" 342 | version = "0.1.22" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 345 | dependencies = [ 346 | "tinyvec", 347 | ] 348 | 349 | [[package]] 350 | name = "unicode-segmentation" 351 | version = "1.10.1" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 354 | 355 | [[package]] 356 | name = "unicode-xid" 357 | version = "0.2.4" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 360 | 361 | [[package]] 362 | name = "url" 363 | version = "2.4.1" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 366 | dependencies = [ 367 | "form_urlencoded", 368 | "idna", 369 | "percent-encoding", 370 | ] 371 | 372 | [[package]] 373 | name = "version_check" 374 | version = "0.9.4" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 377 | 378 | [[package]] 379 | name = "wasm-encoder" 380 | version = "0.29.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" 383 | dependencies = [ 384 | "leb128", 385 | ] 386 | 387 | [[package]] 388 | name = "wasm-metadata" 389 | version = "0.8.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "36e5156581ff4a302405c44ca7c85347563ca431d15f1a773f12c9c7b9a6cdc9" 392 | dependencies = [ 393 | "anyhow", 394 | "indexmap", 395 | "serde", 396 | "wasm-encoder", 397 | "wasmparser", 398 | ] 399 | 400 | [[package]] 401 | name = "wasmparser" 402 | version = "0.107.0" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" 405 | dependencies = [ 406 | "indexmap", 407 | "semver", 408 | ] 409 | 410 | [[package]] 411 | name = "wit-bindgen" 412 | version = "0.8.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "392d16e9e46cc7ca98125bc288dd5e4db469efe8323d3e0dac815ca7f2398522" 415 | dependencies = [ 416 | "bitflags 2.4.1", 417 | "wit-bindgen-rust-macro", 418 | ] 419 | 420 | [[package]] 421 | name = "wit-bindgen-core" 422 | version = "0.8.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "d422d36cbd78caa0e18c3371628447807c66ee72466b69865ea7e33682598158" 425 | dependencies = [ 426 | "anyhow", 427 | "wit-component", 428 | "wit-parser", 429 | ] 430 | 431 | [[package]] 432 | name = "wit-bindgen-rust" 433 | version = "0.8.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "9b76db68264f5d2089dc4652581236d8e75c5b89338de6187716215fd0e68ba3" 436 | dependencies = [ 437 | "heck", 438 | "wasm-metadata", 439 | "wit-bindgen-core", 440 | "wit-bindgen-rust-lib", 441 | "wit-component", 442 | ] 443 | 444 | [[package]] 445 | name = "wit-bindgen-rust-lib" 446 | version = "0.8.0" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "1c50f334bc08b0903a43387f6eea6ef6aa9eb2a085729f1677b29992ecef20ba" 449 | dependencies = [ 450 | "heck", 451 | "wit-bindgen-core", 452 | ] 453 | 454 | [[package]] 455 | name = "wit-bindgen-rust-macro" 456 | version = "0.8.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "ced38a5e174940c6a41ae587babeadfd2e2c2dc32f3b6488bcdca0e8922cf3f3" 459 | dependencies = [ 460 | "anyhow", 461 | "proc-macro2", 462 | "syn 2.0.38", 463 | "wit-bindgen-core", 464 | "wit-bindgen-rust", 465 | "wit-component", 466 | ] 467 | 468 | [[package]] 469 | name = "wit-component" 470 | version = "0.11.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "7cbd4c7f8f400327c482c88571f373844b7889e61460650d650fc5881bb3575c" 473 | dependencies = [ 474 | "anyhow", 475 | "bitflags 1.3.2", 476 | "indexmap", 477 | "log", 478 | "wasm-encoder", 479 | "wasm-metadata", 480 | "wasmparser", 481 | "wit-parser", 482 | ] 483 | 484 | [[package]] 485 | name = "wit-parser" 486 | version = "0.8.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "6daec9f093dbaea0e94043eeb92ece327bbbe70c86b1f41aca9bbfefd7f050f0" 489 | dependencies = [ 490 | "anyhow", 491 | "id-arena", 492 | "indexmap", 493 | "log", 494 | "pulldown-cmark", 495 | "semver", 496 | "unicode-xid", 497 | "url", 498 | ] 499 | -------------------------------------------------------------------------------- /helloworld-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "helloworld-rs" 3 | authors = ["Sohan <1119120+sohanmaheshwar@users.noreply.github.com>"] 4 | description = "A Hello World Serverless AI app, written in Rust" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = [ "cdylib" ] 10 | 11 | [dependencies] 12 | # Useful crate to handle errors. 13 | anyhow = "1" 14 | # Crate to simplify working with bytes. 15 | bytes = "1" 16 | # General-purpose crate with common HTTP types. 17 | http = "0.2" 18 | # The Spin SDK. 19 | spin-sdk = { git = "https://github.com/fermyon/spin", tag = "v1.5.1" } 20 | 21 | [workspace] 22 | -------------------------------------------------------------------------------- /helloworld-rs/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = "1" 2 | authors = ["Sohan <1119120+sohanmaheshwar@users.noreply.github.com>"] 3 | description = "A Hello World Serverless AI app, written in Rust" 4 | name = "helloworld-rs" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "helloworld-rs" 10 | source = "target/wasm32-wasi/release/helloworld_rs.wasm" 11 | allowed_http_hosts = [] 12 | ai_models = ["llama2-chat"] 13 | [component.trigger] 14 | route = "/..." 15 | [component.build] 16 | command = "cargo build --target wasm32-wasi --release" 17 | watch = ["src/**/*.rs", "Cargo.toml"] 18 | -------------------------------------------------------------------------------- /helloworld-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use spin_sdk::{ 3 | http::{Request, Response}, 4 | http_component, llm, 5 | }; 6 | /// A simple Spin HTTP component. 7 | #[http_component] 8 | fn hello_world(req: Request) -> Result { 9 | let model = llm::InferencingModel::Llama2Chat; 10 | let inference = llm::infer(model, "Can you tell me a joke about cats".into()); 11 | if let Ok(result) = &inference { 12 | let text = &result.text; 13 | return Ok(http::Response::builder() 14 | .status(200) 15 | .body(Some(text.to_string().into()))?); 16 | } else { 17 | return Ok(http::Response::builder() 18 | .status(500) 19 | .body(Some("Inference failed".to_string().into()))?); 20 | } 21 | } -------------------------------------------------------------------------------- /helloworld-ts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | target 4 | .spin/ -------------------------------------------------------------------------------- /helloworld-ts/README.md: -------------------------------------------------------------------------------- 1 | ## HTTP-TS template 2 | 3 | This is a simple template to get started with spin-js-sdk using typescript. -------------------------------------------------------------------------------- /helloworld-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "helloworld-ts", 3 | "version": "1.0.0", 4 | "description": "A Hello World Serverless AI application, written in Typescript", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npx webpack --mode=production && mkdir -p target && spin js2wasm -o target/helloworld-ts.wasm dist/spin.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "ts-loader": "^9.4.1", 15 | "typescript": "^4.8.4", 16 | "webpack": "^5.74.0", 17 | "webpack-cli": "^4.10.0" 18 | }, 19 | "dependencies": { 20 | "@fermyon/spin-sdk": "0.6.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /helloworld-ts/runtime-config.toml: -------------------------------------------------------------------------------- 1 | [llm_compute] 2 | type = "remote_http" 3 | url = "https://fermyon-cloud-gpu-a1egrexw.fermyon.app" 4 | auth_token = "9a41ee3e-292c-4fc7-a3ec-b5f527b3b84f" -------------------------------------------------------------------------------- /helloworld-ts/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = "1" 2 | authors = ["Sohan <1119120+sohanmaheshwar@users.noreply.github.com>"] 3 | description = "A Hello World Serverless AI application, written in Typescript" 4 | name = "helloworld-ts" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "helloworld-ts" 10 | source = "target/helloworld-ts.wasm" 11 | exclude_files = ["**/node_modules"] 12 | ai_models = ["llama2-chat"] 13 | [component.trigger] 14 | route = "/..." 15 | [component.build] 16 | command = "npm run build" 17 | -------------------------------------------------------------------------------- /helloworld-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Llm, InferencingModels, HandleRequest, HttpRequest, HttpResponse } from "@fermyon/spin-sdk" 2 | const model = InferencingModels.Llama2Chat 3 | export const handleRequest: HandleRequest = async function (request: HttpRequest): Promise { 4 | const prompt = "Tell me a joke about cats" 5 | const out = Llm.infer(model, prompt) 6 | return { 7 | status: 200, 8 | body: out.text 9 | } 10 | } -------------------------------------------------------------------------------- /helloworld-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "module": "es6", 6 | "target": "es5", 7 | "jsx": "react", 8 | "skipLibCheck": true, 9 | "lib": ["ES2015"], 10 | "allowJs": true, 11 | "strict": true, 12 | "noImplicitReturns": true, 13 | "moduleResolution": "node" 14 | } 15 | } -------------------------------------------------------------------------------- /helloworld-ts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.tsx?$/, 9 | use: 'ts-loader', 10 | exclude: /node_modules/, 11 | }, 12 | ], 13 | }, 14 | resolve: { 15 | extensions: ['.tsx', '.ts', '.js'], 16 | }, 17 | output: { 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'spin.js', 20 | library: 'spin' 21 | }, 22 | optimization: { 23 | minimize: false 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /newsfeeder-ts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | target 4 | .spin/ -------------------------------------------------------------------------------- /newsfeeder-ts/README.md: -------------------------------------------------------------------------------- 1 | ## News Feeder (TypeScript) 2 | 3 | > The `package.json` needs updating when the final TS/JS SDK is released. Right now you will need to hand edit it to point to the SDK. 4 | 5 | This is an example of building a prompt from an RSS feed, and then sending it to Fermyon Serverless AI. It is written in TypeScript 6 | 7 | It uses the HTTP rest extension to get an RSS feed from TechCrunch, then it parses the result and builds a prompt, which it sends to Serverless AI. 8 | 9 | Please check the repo's [README](../README.md#prerequisites) for prerequisites for running this example. 10 | 11 | ## Building and Running 12 | 13 | ```bash 14 | $ npm install 15 | $ spin build --up 16 | ``` 17 | 18 | > Note: If you are using the Cloud GPU component, remember to reference the `runtime-config.toml` file, e.g.: `spin build --up --runtime-config-file ./runtime-config.toml`. 19 | 20 | ## Deploy the application to Fermyon Cloud 21 | 22 | ```bash 23 | $ cd api 24 | $ spin deploy 25 | ``` 26 | -------------------------------------------------------------------------------- /newsfeeder-ts/newsfeeder.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: core.spinoperator.dev/v1alpha1 2 | kind: SpinApp 3 | metadata: 4 | name: newsfeeder-ts 5 | spec: 6 | image: "ghcr.io/fermyon/newsfeeder-ts:v0.0.1" 7 | executor: containerd-shim-spin 8 | replicas: 2 9 | -------------------------------------------------------------------------------- /newsfeeder-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "newsfeeder", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npx webpack --mode=production && mkdir -p target && spin js2wasm -o target/newsfeeder.wasm dist/spin.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "ts-loader": "^9.4.1", 15 | "typescript": "^4.8.4", 16 | "webpack": "^5.74.0", 17 | "webpack-cli": "^4.10.0" 18 | }, 19 | "dependencies": { 20 | "@fermyon/spin-sdk": "0.6.0-rc.0", 21 | "htmlparser2": "^9.0.0", 22 | "rss-parser": "^3.13.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /newsfeeder-ts/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = "1" 2 | authors = ["Matt Butcher "] 3 | description = "" 4 | name = "newsfeeder" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "newsfeeder" 10 | source = "target/newsfeeder.wasm" 11 | exclude_files = ["**/node_modules"] 12 | allowed_http_hosts = ["techcrunch.com"] 13 | ai_models = ["llama2-chat"] 14 | [component.trigger] 15 | route = "/..." 16 | [component.build] 17 | command = "npm run build" 18 | -------------------------------------------------------------------------------- /newsfeeder-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Llm, InferencingModels, HandleRequest, HttpRequest, HttpResponse } from "@fermyon/spin-sdk" 2 | import * as htmlparser2 from "htmlparser2" 3 | 4 | const dec = new TextDecoder() 5 | const model = InferencingModels.Llama2Chat 6 | const MAX_ITEMS = 10; 7 | 8 | export const handleRequest: HandleRequest = async function (request: HttpRequest): Promise { 9 | 10 | const data = await fetch('https://techcrunch.com/feed/') 11 | const body = dec.decode(await data.arrayBuffer()) 12 | 13 | let feed = await htmlparser2.parseFeed(body) 14 | if (feed == null) { 15 | return { 16 | status: 500, 17 | body: "Internal error fetching feed" 18 | } 19 | } 20 | console.log(feed.title) 21 | var prompt = "Here are today's TechCrunch news items. In the style of a news brief, summarize in one paragraph the items that have to do with cloud.\n\n" 22 | const maxItems = feed.items.length > MAX_ITEMS ? MAX_ITEMS : feed.items.length 23 | for (let index = 0; index < maxItems; index++) { 24 | const element = feed.items[index] 25 | prompt += `Item: ${element.title}\n${element.description}\n\n` 26 | } 27 | 28 | console.log(prompt) 29 | let response = Llm.infer(model, prompt, { maxTokens: 1000 }) 30 | console.log(`usage: in=${response.usage.promptTokenCount}, out=${response.usage.generatedTokenCount}`) 31 | 32 | return { 33 | status: 200, 34 | body: response.text 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /newsfeeder-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "module": "es6", 6 | "target": "es5", 7 | "jsx": "react", 8 | "skipLibCheck": true, 9 | "lib": [ 10 | "ES2015" 11 | ], 12 | "allowJs": true, 13 | "strict": true, 14 | "noImplicitReturns": true, 15 | "moduleResolution": "node" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /newsfeeder-ts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.tsx?$/, 9 | use: 'ts-loader', 10 | exclude: /node_modules/, 11 | }, 12 | ], 13 | }, 14 | resolve: { 15 | extensions: ['.tsx', '.ts', '.js'], 16 | }, 17 | output: { 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'spin.js', 20 | library: 'spin' 21 | }, 22 | optimization: { 23 | minimize: false 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /openapi-rs/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .spin/ 3 | swagger-ui/openapi.yml -------------------------------------------------------------------------------- /openapi-rs/README.md: -------------------------------------------------------------------------------- 1 | ## OpenAPI Component (Rust) 2 | 3 | This is an example of wrapping Spin's LLM SDK in an OpenAPI contract. The API component provides some sensible defaults that are easily overridden in the request's JSON payload. It also contains a Swagger UI component that you can use to interact with the API. 4 | 5 | Please check the repo's [README](../README.md#prerequisites) for prerequisites for running this example. 6 | 7 | ## Build and Running 8 | 9 | ```bash 10 | $ spin build --up 11 | ``` 12 | 13 | > Note: If you are using the Cloud GPU component, remember to reference the `runtime-config.toml` file, e.g.: `spin build --up --runtime-config-file ./runtime-config.toml`. 14 | 15 | Swagger UI should now be available at http://127.0.0.1:3000. 16 | 17 | ## Deploy the application to Fermyon Cloud 18 | 19 | ```bash 20 | $ cd api 21 | $ spin deploy 22 | ``` 23 | 24 | Make sure to change the url-reference in the `./client/src/main.rs`` file 25 | -------------------------------------------------------------------------------- /openapi-rs/api/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.75" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.1.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "2.4.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 28 | 29 | [[package]] 30 | name = "bytes" 31 | version = "1.4.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 34 | 35 | [[package]] 36 | name = "fnv" 37 | version = "1.0.7" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 40 | 41 | [[package]] 42 | name = "form_urlencoded" 43 | version = "1.2.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 46 | dependencies = [ 47 | "percent-encoding", 48 | ] 49 | 50 | [[package]] 51 | name = "hashbrown" 52 | version = "0.12.3" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 55 | 56 | [[package]] 57 | name = "heck" 58 | version = "0.4.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 61 | dependencies = [ 62 | "unicode-segmentation", 63 | ] 64 | 65 | [[package]] 66 | name = "http" 67 | version = "0.2.9" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 70 | dependencies = [ 71 | "bytes", 72 | "fnv", 73 | "itoa", 74 | ] 75 | 76 | [[package]] 77 | name = "id-arena" 78 | version = "2.2.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 81 | 82 | [[package]] 83 | name = "idna" 84 | version = "0.4.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 87 | dependencies = [ 88 | "unicode-bidi", 89 | "unicode-normalization", 90 | ] 91 | 92 | [[package]] 93 | name = "indexmap" 94 | version = "1.9.3" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 97 | dependencies = [ 98 | "autocfg", 99 | "hashbrown", 100 | "serde", 101 | ] 102 | 103 | [[package]] 104 | name = "itoa" 105 | version = "1.0.9" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 108 | 109 | [[package]] 110 | name = "leb128" 111 | version = "0.2.5" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 114 | 115 | [[package]] 116 | name = "log" 117 | version = "0.4.20" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 120 | 121 | [[package]] 122 | name = "memchr" 123 | version = "2.6.2" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" 126 | 127 | [[package]] 128 | name = "openapi" 129 | version = "0.1.0" 130 | dependencies = [ 131 | "anyhow", 132 | "bytes", 133 | "http", 134 | "serde", 135 | "serde_json", 136 | "spin-sdk", 137 | ] 138 | 139 | [[package]] 140 | name = "percent-encoding" 141 | version = "2.3.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 144 | 145 | [[package]] 146 | name = "proc-macro2" 147 | version = "1.0.66" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 150 | dependencies = [ 151 | "unicode-ident", 152 | ] 153 | 154 | [[package]] 155 | name = "pulldown-cmark" 156 | version = "0.8.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" 159 | dependencies = [ 160 | "bitflags 1.3.2", 161 | "memchr", 162 | "unicase", 163 | ] 164 | 165 | [[package]] 166 | name = "quote" 167 | version = "1.0.33" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 170 | dependencies = [ 171 | "proc-macro2", 172 | ] 173 | 174 | [[package]] 175 | name = "routefinder" 176 | version = "0.5.3" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "94f8f99b10dedd317514253dda1fa7c14e344aac96e1f78149a64879ce282aca" 179 | dependencies = [ 180 | "smartcow", 181 | "smartstring", 182 | ] 183 | 184 | [[package]] 185 | name = "ryu" 186 | version = "1.0.15" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 189 | 190 | [[package]] 191 | name = "semver" 192 | version = "1.0.18" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" 195 | 196 | [[package]] 197 | name = "serde" 198 | version = "1.0.188" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 201 | dependencies = [ 202 | "serde_derive", 203 | ] 204 | 205 | [[package]] 206 | name = "serde_derive" 207 | version = "1.0.188" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 210 | dependencies = [ 211 | "proc-macro2", 212 | "quote", 213 | "syn 2.0.29", 214 | ] 215 | 216 | [[package]] 217 | name = "serde_json" 218 | version = "1.0.105" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" 221 | dependencies = [ 222 | "itoa", 223 | "ryu", 224 | "serde", 225 | ] 226 | 227 | [[package]] 228 | name = "smartcow" 229 | version = "0.2.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "656fcb1c1fca8c4655372134ce87d8afdf5ec5949ebabe8d314be0141d8b5da2" 232 | dependencies = [ 233 | "smartstring", 234 | ] 235 | 236 | [[package]] 237 | name = "smartstring" 238 | version = "1.0.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" 241 | dependencies = [ 242 | "autocfg", 243 | "static_assertions", 244 | "version_check", 245 | ] 246 | 247 | [[package]] 248 | name = "spin-macro" 249 | version = "0.1.0" 250 | source = "git+https://github.com/fermyon/spin?tag=v1.5.0#ca08dd933de32fe09f4c318fbbc1f04853f2085f" 251 | dependencies = [ 252 | "anyhow", 253 | "bytes", 254 | "http", 255 | "proc-macro2", 256 | "quote", 257 | "syn 1.0.109", 258 | ] 259 | 260 | [[package]] 261 | name = "spin-sdk" 262 | version = "1.5.0" 263 | source = "git+https://github.com/fermyon/spin?tag=v1.5.0#ca08dd933de32fe09f4c318fbbc1f04853f2085f" 264 | dependencies = [ 265 | "anyhow", 266 | "bytes", 267 | "form_urlencoded", 268 | "http", 269 | "routefinder", 270 | "spin-macro", 271 | "thiserror", 272 | "wit-bindgen", 273 | ] 274 | 275 | [[package]] 276 | name = "static_assertions" 277 | version = "1.1.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 280 | 281 | [[package]] 282 | name = "syn" 283 | version = "1.0.109" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 286 | dependencies = [ 287 | "proc-macro2", 288 | "quote", 289 | "unicode-ident", 290 | ] 291 | 292 | [[package]] 293 | name = "syn" 294 | version = "2.0.29" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" 297 | dependencies = [ 298 | "proc-macro2", 299 | "quote", 300 | "unicode-ident", 301 | ] 302 | 303 | [[package]] 304 | name = "thiserror" 305 | version = "1.0.47" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" 308 | dependencies = [ 309 | "thiserror-impl", 310 | ] 311 | 312 | [[package]] 313 | name = "thiserror-impl" 314 | version = "1.0.47" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" 317 | dependencies = [ 318 | "proc-macro2", 319 | "quote", 320 | "syn 2.0.29", 321 | ] 322 | 323 | [[package]] 324 | name = "tinyvec" 325 | version = "1.6.0" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 328 | dependencies = [ 329 | "tinyvec_macros", 330 | ] 331 | 332 | [[package]] 333 | name = "tinyvec_macros" 334 | version = "0.1.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 337 | 338 | [[package]] 339 | name = "unicase" 340 | version = "2.7.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 343 | dependencies = [ 344 | "version_check", 345 | ] 346 | 347 | [[package]] 348 | name = "unicode-bidi" 349 | version = "0.3.13" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 352 | 353 | [[package]] 354 | name = "unicode-ident" 355 | version = "1.0.11" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 358 | 359 | [[package]] 360 | name = "unicode-normalization" 361 | version = "0.1.22" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 364 | dependencies = [ 365 | "tinyvec", 366 | ] 367 | 368 | [[package]] 369 | name = "unicode-segmentation" 370 | version = "1.10.1" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 373 | 374 | [[package]] 375 | name = "unicode-xid" 376 | version = "0.2.4" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 379 | 380 | [[package]] 381 | name = "url" 382 | version = "2.4.1" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 385 | dependencies = [ 386 | "form_urlencoded", 387 | "idna", 388 | "percent-encoding", 389 | ] 390 | 391 | [[package]] 392 | name = "version_check" 393 | version = "0.9.4" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 396 | 397 | [[package]] 398 | name = "wasm-encoder" 399 | version = "0.29.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" 402 | dependencies = [ 403 | "leb128", 404 | ] 405 | 406 | [[package]] 407 | name = "wasm-metadata" 408 | version = "0.8.0" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "36e5156581ff4a302405c44ca7c85347563ca431d15f1a773f12c9c7b9a6cdc9" 411 | dependencies = [ 412 | "anyhow", 413 | "indexmap", 414 | "serde", 415 | "wasm-encoder", 416 | "wasmparser", 417 | ] 418 | 419 | [[package]] 420 | name = "wasmparser" 421 | version = "0.107.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" 424 | dependencies = [ 425 | "indexmap", 426 | "semver", 427 | ] 428 | 429 | [[package]] 430 | name = "wit-bindgen" 431 | version = "0.8.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "392d16e9e46cc7ca98125bc288dd5e4db469efe8323d3e0dac815ca7f2398522" 434 | dependencies = [ 435 | "bitflags 2.4.0", 436 | "wit-bindgen-rust-macro", 437 | ] 438 | 439 | [[package]] 440 | name = "wit-bindgen-core" 441 | version = "0.8.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "d422d36cbd78caa0e18c3371628447807c66ee72466b69865ea7e33682598158" 444 | dependencies = [ 445 | "anyhow", 446 | "wit-component", 447 | "wit-parser", 448 | ] 449 | 450 | [[package]] 451 | name = "wit-bindgen-rust" 452 | version = "0.8.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "9b76db68264f5d2089dc4652581236d8e75c5b89338de6187716215fd0e68ba3" 455 | dependencies = [ 456 | "heck", 457 | "wasm-metadata", 458 | "wit-bindgen-core", 459 | "wit-bindgen-rust-lib", 460 | "wit-component", 461 | ] 462 | 463 | [[package]] 464 | name = "wit-bindgen-rust-lib" 465 | version = "0.8.0" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "1c50f334bc08b0903a43387f6eea6ef6aa9eb2a085729f1677b29992ecef20ba" 468 | dependencies = [ 469 | "heck", 470 | "wit-bindgen-core", 471 | ] 472 | 473 | [[package]] 474 | name = "wit-bindgen-rust-macro" 475 | version = "0.8.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "ced38a5e174940c6a41ae587babeadfd2e2c2dc32f3b6488bcdca0e8922cf3f3" 478 | dependencies = [ 479 | "anyhow", 480 | "proc-macro2", 481 | "syn 2.0.29", 482 | "wit-bindgen-core", 483 | "wit-bindgen-rust", 484 | "wit-component", 485 | ] 486 | 487 | [[package]] 488 | name = "wit-component" 489 | version = "0.11.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "7cbd4c7f8f400327c482c88571f373844b7889e61460650d650fc5881bb3575c" 492 | dependencies = [ 493 | "anyhow", 494 | "bitflags 1.3.2", 495 | "indexmap", 496 | "log", 497 | "wasm-encoder", 498 | "wasm-metadata", 499 | "wasmparser", 500 | "wit-parser", 501 | ] 502 | 503 | [[package]] 504 | name = "wit-parser" 505 | version = "0.8.0" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "6daec9f093dbaea0e94043eeb92ece327bbbe70c86b1f41aca9bbfefd7f050f0" 508 | dependencies = [ 509 | "anyhow", 510 | "id-arena", 511 | "indexmap", 512 | "log", 513 | "pulldown-cmark", 514 | "semver", 515 | "unicode-xid", 516 | "url", 517 | ] 518 | -------------------------------------------------------------------------------- /openapi-rs/api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openapi" 3 | authors = ["Justin Pflueger "] 4 | description = "" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = [ "cdylib" ] 10 | 11 | [dependencies] 12 | anyhow = "1" 13 | bytes = "1" 14 | http = "0.2" 15 | serde = { version = "1.0.188", features = ["derive"] } 16 | serde_json = "1.0.105" 17 | spin-sdk = { git = "https://github.com/fermyon/spin", tag = "v1.5.0" } 18 | 19 | [workspace] 20 | -------------------------------------------------------------------------------- /openapi-rs/api/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use bytes::Bytes; 3 | use serde::{Deserialize, Serialize}; 4 | use spin_sdk::{ 5 | http::{Params, Request, Response, Router}, 6 | http_component, 7 | }; 8 | 9 | // constants for inferencing option defaults 10 | const DEFAULT_MAX_TOKENS: u32 = 75; 11 | const DEFAULT_REPEAT_PENALTY: f32 = 1.1; 12 | const DEFAULT_REPEAT_PENALTY_LAST_N_TOKEN_COUNT: u32 = 64; 13 | const DEFAULT_TEMPERATURE: f32 = 0.0; 14 | const DEFAULT_TOP_K: u32 = 40; 15 | const DEFAULT_TOP_P: f32 = 0.9; 16 | const DEFAULT_MODEL: &str = "llama2-chat"; 17 | const DEFAULT_SYSTEM_PROMPT: &str = "You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature. 18 | 19 | If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information."; 20 | 21 | // api data model for inferencing 22 | #[derive(Debug, Serialize, Deserialize)] 23 | pub struct InferRequest { 24 | pub model: Option, 25 | pub system_prompt: Option, 26 | pub user_prompt: String, 27 | pub options: Option, 28 | } 29 | 30 | #[derive(Debug, Serialize, Deserialize)] 31 | pub struct InferRequestOptions { 32 | pub max_tokens: Option, 33 | pub repeat_penalty: Option, 34 | pub repeat_penalty_last_n_token_count: Option, 35 | pub temperature: Option, 36 | pub top_k: Option, 37 | pub top_p: Option, 38 | } 39 | 40 | impl Default for InferRequestOptions { 41 | fn default() -> Self { 42 | InferRequestOptions { 43 | max_tokens: Some(DEFAULT_MAX_TOKENS), 44 | repeat_penalty: Some(DEFAULT_REPEAT_PENALTY), 45 | repeat_penalty_last_n_token_count: Some(DEFAULT_REPEAT_PENALTY_LAST_N_TOKEN_COUNT), 46 | temperature: Some(DEFAULT_TEMPERATURE), 47 | top_k: Some(DEFAULT_TOP_K), 48 | top_p: Some(DEFAULT_TOP_P), 49 | } 50 | } 51 | } 52 | 53 | impl Into for InferRequestOptions { 54 | fn into(self) -> spin_sdk::llm::InferencingParams { 55 | spin_sdk::llm::InferencingParams { 56 | //TODO: set constants for these defaults 57 | max_tokens: self.max_tokens.unwrap_or(DEFAULT_MAX_TOKENS), 58 | repeat_penalty: self.repeat_penalty.unwrap_or(DEFAULT_REPEAT_PENALTY), 59 | repeat_penalty_last_n_token_count: self 60 | .repeat_penalty_last_n_token_count 61 | .unwrap_or(DEFAULT_REPEAT_PENALTY_LAST_N_TOKEN_COUNT), 62 | temperature: self.temperature.unwrap_or(DEFAULT_TEMPERATURE), 63 | top_k: self.top_k.unwrap_or(DEFAULT_TOP_K), 64 | top_p: self.top_p.unwrap_or(DEFAULT_TOP_P), 65 | } 66 | } 67 | } 68 | 69 | #[derive(Debug, Serialize, Deserialize)] 70 | pub struct InferResponse { 71 | pub text: String, 72 | pub usage: InferResponseUsage, 73 | } 74 | 75 | #[derive(Debug, Serialize, Deserialize)] 76 | pub struct InferResponseUsage { 77 | pub prompt_token_count: u32, 78 | pub generated_token_count: u32, 79 | } 80 | 81 | impl From for InferResponse { 82 | fn from(result: spin_sdk::llm::InferencingResult) -> Self { 83 | InferResponse { 84 | text: result.text, 85 | usage: InferResponseUsage { 86 | prompt_token_count: result.usage.prompt_token_count, 87 | generated_token_count: result.usage.generated_token_count, 88 | }, 89 | } 90 | } 91 | } 92 | 93 | #[http_component] 94 | fn handle_api(req: Request) -> Result { 95 | let component_route = req 96 | .headers() 97 | .get("spin-component-route") 98 | .unwrap() 99 | .to_str()?; 100 | let mut router = Router::new(); 101 | router.post(&format!("{}/infer", component_route), handle_infer); 102 | router.handle(req) 103 | } 104 | 105 | fn handle_infer(req: Request, _params: Params) -> Result { 106 | // parse the request 107 | let request: InferRequest = 108 | serde_json::from_slice(req.body().as_ref().unwrap_or(&Bytes::new()))?; 109 | let model: spin_sdk::llm::InferencingModel = match request.model.as_deref() { 110 | Some("llama2-chat") => spin_sdk::llm::InferencingModel::Llama2Chat, 111 | Some("codellama-instruct") => spin_sdk::llm::InferencingModel::CodellamaInstruct, 112 | Some(other) => spin_sdk::llm::InferencingModel::Other(other), 113 | None => spin_sdk::llm::InferencingModel::Other(DEFAULT_MODEL), 114 | }; 115 | let options: spin_sdk::llm::InferencingParams = request.options.unwrap_or_default().into(); 116 | 117 | let system_prompt = request 118 | .system_prompt 119 | .unwrap_or(DEFAULT_SYSTEM_PROMPT.to_string()); 120 | let user_prompt = request.user_prompt; 121 | 122 | let prompt = format!( 123 | "[INST] <>\n{}\n<>\n\n{} [/INST]", 124 | system_prompt, user_prompt 125 | ); 126 | 127 | println!("formatted_prompt: {:?}", prompt); 128 | 129 | let inferred_result = spin_sdk::llm::infer_with_options(model, &prompt, options); 130 | 131 | match inferred_result { 132 | Ok(result) => Ok(http::Response::builder() 133 | .status(200) 134 | .header("Content-Type", "application/json") 135 | .body(Some( 136 | serde_json::to_string(&InferResponse::from(result))?.into(), 137 | ))?), 138 | 139 | Err(e) => Ok(http::Response::builder() 140 | .status(500) 141 | .body(Some(e.to_string().into()))?), 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /openapi-rs/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Spin LLM API 4 | version: 1.0.0 5 | description: | 6 | This API provides access to the Spin LLM model. 7 | servers: 8 | - url: /api 9 | paths: 10 | /infer: 11 | post: 12 | tags: 13 | - infer 14 | summary: Request an inference from the LLM model. 15 | requestBody: 16 | description: Request body for inferencing 17 | required: true 18 | content: 19 | application/json: 20 | schema: 21 | type: object 22 | required: 23 | - "user_prompt" 24 | properties: 25 | model: 26 | type: string 27 | title: "Model" 28 | description: "The model to use for generating the response." 29 | default: "llama2-chat" 30 | example: "llama2-chat" 31 | system_prompt: 32 | type: string 33 | title: "System Prompt" 34 | description: "The system prompt." 35 | default: | 36 | You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature. 37 | 38 | If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information. 39 | example: | 40 | You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature. 41 | 42 | If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information. 43 | user_prompt: 44 | type: string 45 | title: "User Prompt" 46 | description: "The user prompt." 47 | example: "What is your name?" 48 | options: 49 | type: object 50 | title: Options 51 | description: "Inferencing options." 52 | properties: 53 | max_tokens: 54 | type: integer 55 | description: "The maximum number of tokens in the generated response." 56 | default: 75 57 | example: 75 58 | repeat_penalty: 59 | type: number 60 | description: "The repeat penalty for generated tokens." 61 | default: 1.1 62 | example: 1.1 63 | repeat_penalty_last_n_token_count: 64 | type: integer 65 | description: "The last N token count for repeat penalty." 66 | default: 64 67 | example: 64 68 | temperature: 69 | type: number 70 | description: "The temperature for text generation." 71 | default: 1.0 72 | example: 1.0 73 | top_k: 74 | type: integer 75 | description: "The top-k value for token selection." 76 | default: 40 77 | example: 40 78 | top_p: 79 | type: number 80 | description: "The top-p value for token selection." 81 | default: 0.9 82 | example: 0.9 83 | responses: 84 | '200': 85 | description: "Successful response" 86 | content: 87 | application/json: 88 | schema: 89 | type: object 90 | properties: 91 | text: 92 | type: string 93 | title: "Text" 94 | description: "The generated text response." 95 | usage: 96 | type: object 97 | properties: 98 | prompt_token_count: 99 | type: integer 100 | title: "Prompt Token Count" 101 | description: "The number of tokens in the prompt." 102 | example: 103 | generated_token_count: 104 | type: integer 105 | title: "Generated Token Count" 106 | description: "The number of tokens generated in the response." 107 | -------------------------------------------------------------------------------- /openapi-rs/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = "1" 2 | authors = ["Justin Pflueger "] 3 | description = "A re-usable Spin component that enables the LLM API to be used as an OpenAPI service." 4 | name = "openapi" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "openapi" 10 | source = "api/target/wasm32-wasi/release/openapi.wasm" 11 | exclude_files = ["**/node_modules"] 12 | ai_models = ["llama2-chat"] 13 | [component.trigger] 14 | route = "/api/..." 15 | [component.build] 16 | command = "cargo build --target wasm32-wasi --release" 17 | workdir = "api" 18 | 19 | [[component]] 20 | source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.0.3/spin_static_fs.wasm", digest = "sha256:38bf971900228222f7f6b2ccee5051f399adca58d71692cdfdea98997965fd0d" } 21 | id = "swagger-ui" 22 | files = [{ source = "swagger-ui", destination = "/" }] 23 | [component.trigger] 24 | route = "/..." 25 | [component.build] 26 | command = "cp openapi.yml swagger-ui/openapi.yml" 27 | -------------------------------------------------------------------------------- /openapi-rs/swagger-ui/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fermyon/ai-examples/4af35e487f95c5019dce3ec22ff7f507b2033c5d/openapi-rs/swagger-ui/favicon-16x16.png -------------------------------------------------------------------------------- /openapi-rs/swagger-ui/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fermyon/ai-examples/4af35e487f95c5019dce3ec22ff7f507b2033c5d/openapi-rs/swagger-ui/favicon-32x32.png -------------------------------------------------------------------------------- /openapi-rs/swagger-ui/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | overflow: -moz-scrollbars-vertical; 4 | overflow-y: scroll; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | background: #fafafa; 16 | } 17 | -------------------------------------------------------------------------------- /openapi-rs/swagger-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /openapi-rs/swagger-ui/oauth2-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Swagger UI: OAuth2 Redirect 5 | 6 | 7 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /openapi-rs/swagger-ui/swagger-initializer.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | // 3 | 4 | // the following lines will be replaced by docker/configurator, when it runs in a docker-container 5 | window.ui = SwaggerUIBundle({ 6 | url: "/openapi.yml", 7 | dom_id: '#swagger-ui', 8 | deepLinking: true, 9 | presets: [ 10 | SwaggerUIBundle.presets.apis, 11 | SwaggerUIStandalonePreset 12 | ], 13 | plugins: [ 14 | SwaggerUIBundle.plugins.DownloadUrl 15 | ], 16 | layout: "StandaloneLayout" 17 | }); 18 | 19 | // 20 | }; 21 | -------------------------------------------------------------------------------- /sentiment-analysis-assets/dynamic.js: -------------------------------------------------------------------------------- 1 | // Listen for the Enter key being pressed 2 | document.addEventListener("keydown", function (event) { 3 | if (event.keyCode === 13) { 4 | newCard(); 5 | } 6 | }); 7 | 8 | var globalCardCount = 0; 9 | var runningInference = false; 10 | 11 | function newCard() { 12 | if (runningInference) { 13 | console.log("Already running inference, please wait..."); 14 | setAlert("Already running inference, please wait..."); 15 | return; 16 | } 17 | var inputElement = document.getElementById("sentence-input"); 18 | var sentence = inputElement.value; 19 | if (sentence === "") { 20 | console.log("Please enter a sentence to analyze"); 21 | setAlert("Please enter a sentence to analyze"); 22 | return; 23 | } 24 | inputElement.value = ""; 25 | 26 | var cardIndex = globalCardCount; 27 | globalCardCount++; 28 | var newCard = document.createElement("div"); 29 | newCard.id = "card-" + cardIndex; 30 | newCard.innerHTML = ` 31 |
32 |
33 |
${sentence}
34 |
35 | 36 |
37 |
38 |
39 | `; 40 | document.getElementById("sentence-input").before(newCard); 41 | 42 | console.log("Running inference on sentence: " + sentence); 43 | runningInference = true; 44 | fetch("/api/sentiment-analysis", { 45 | method: "POST", 46 | headers: { 47 | "Content-Type": "application/json", 48 | }, 49 | body: JSON.stringify({ sentence: sentence }), 50 | }) 51 | .then((response) => response.json()) 52 | .then((data) => { 53 | console.log(data); 54 | updateCard(cardIndex, sentence, data.sentiment); 55 | }) 56 | .catch((error) => { 57 | console.log(error); 58 | }); 59 | } 60 | 61 | function updateCard(cardIndex, sentence, sentiment) { 62 | badge = ""; 63 | if (sentiment === "positive") { 64 | badge = `Positive`; 65 | } else if (sentiment === "negative") { 66 | badge = `Negative`; 67 | } else if (sentiment === "neutral") { 68 | badge = `Neutral`; 69 | } else { 70 | badge = `Unsure`; 71 | } 72 | var cardElement = document.getElementById("card-" + cardIndex); 73 | cardElement.innerHTML = ` 74 |
75 |
76 |
${sentence}
77 |
78 | ${badge} 79 |
80 |
81 |
82 | `; 83 | runningInference = false; 84 | } 85 | 86 | function setAlert(msg) { 87 | var alertElement = document.getElementById("alert"); 88 | alertElement.innerHTML = ` 89 |
90 | 91 | ${msg} 92 |
93 | `; 94 | setTimeout(function () { 95 | alertElement.innerHTML = ""; 96 | }, 3000); 97 | } 98 | -------------------------------------------------------------------------------- /sentiment-analysis-assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sentiment Analyzer 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
25 | 35 |
36 |
37 |

38 | This Sentiment Analyzer is a demonstration of how you can use Fermyon 39 | Serverless AI to easily make an AI-powered API. When you type in a 40 | sentence it is sent to a Spin app running in the Fermyon Cloud, 41 | inferencing is performed using the Fermyon serverless AI feature, and 42 | the response is cached in a Fermyon key/value store. 43 |

44 |

45 | Note that LLM's are not perfect and the sentiment analysis performed 46 | by this application is not guaranteed to be perfect. 47 |

48 |

49 | To get started type a sentence below and press 50 | enter. 51 |

52 | 53 |
54 | 60 |
61 | 62 |
63 |
64 |
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /sentiment-analysis-py/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.wasm 3 | .spin -------------------------------------------------------------------------------- /sentiment-analysis-py/README.md: -------------------------------------------------------------------------------- 1 | # Sentiment Analysis 2 | 3 | This example is based on the newer Spin Python SDK based on [`componentize-py`](https://github.com/bytecodealliance/componentize-py). 4 | 5 | This repository contains an API that performs sentiment analysis and a simple UI to interact with it. 6 | 7 | Please check the repo's [README](../README.md#prerequisites) for prerequisites for running this example. 8 | 9 | ## Build and Running 10 | 11 | ```bash 12 | $ python3 -m venv venv 13 | $ source venv/bin/activate 14 | $ pip3 install -r requirements.txt 15 | $ spin build --up 16 | ``` 17 | 18 | > Note: If you are using the Cloud GPU component, remember to reference the `runtime-config.toml` file, e.g.: `spin build --up --runtime-config-file ./runtime-config.toml`. 19 | 20 | ## Test the Application 21 | 22 | ```bash 23 | $ curl -X POST --data '{"sentence":"Everything is awesome!"}' https://sentiment-analysis-abc-xyz.fermyon.app/ 24 | ``` 25 | 26 | You can access the UI at http://localhost:3000. The KV-Explorer can be found at http://localhost:3000/internal/kv-explorer. 27 | 28 | ## Deploy the application to Fermyon Cloud 29 | 30 | ```bash 31 | $ spin deploy 32 | ``` 33 | -------------------------------------------------------------------------------- /sentiment-analysis-py/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http, llm, key_value 2 | from spin_sdk.http import Request, Response 3 | import json 4 | 5 | PROMPT="""<> 6 | You are a bot that generates sentiment analysis responses. Respond with a single positive, negative, or neutral. 7 | <> 8 | [INST] 9 | Follow the pattern of the following examples: 10 | 11 | User: Hi, my name is Bob 12 | Bot: neutral 13 | 14 | User: I am so happy today 15 | Bot: positive 16 | 17 | User: I am so sad today 18 | Bot: negative 19 | 20 | [/INST] 21 | User: """ 22 | 23 | class IncomingHandler(http.IncomingHandler): 24 | def handle_request(self, request: Request) -> Response: 25 | # Extracting the sentence from the request_body 26 | request_body=json.loads(request.body) 27 | sentence=request_body["sentence"].strip() 28 | print("Performing sentiment analysis on: " + sentence) 29 | 30 | # Open the default KV store 31 | store = key_value.open_default() 32 | 33 | # Check if the sentence is already in the KV store 34 | if store.exists(sentence) is False: 35 | result=llm.infer("llama2-chat", PROMPT+sentence).text 36 | print("Raw result: " + result) 37 | sentiment = get_sentiment_from_sentence(result) 38 | print("Storing result in the KV store") 39 | store.set(sentence, str.encode(sentiment)) 40 | else: 41 | sentiment = store.get(sentence).decode() 42 | print("Found a cached result") 43 | 44 | response_body=json.dumps({"sentiment": sentiment}) 45 | 46 | return Response(200, 47 | {"content-type": "application/json"}, 48 | bytes(response_body, "utf-8")) 49 | 50 | def get_sentiment_from_sentence(sentence) -> str: 51 | words = sentence.lower().split() 52 | sentiments = ["positive", "negative", "neutral"] 53 | result = next((word for word in sentiments if word in words), None) 54 | 55 | if result is not None: 56 | return result 57 | else: 58 | print("Inconclusive, returning 'neutral'") 59 | return "neutral" 60 | -------------------------------------------------------------------------------- /sentiment-analysis-py/assets/dynamic.js: -------------------------------------------------------------------------------- 1 | // Listen for the Enter key being pressed 2 | document.addEventListener("keydown", function (event) { 3 | if (event.keyCode === 13) { 4 | newCard(); 5 | } 6 | }); 7 | 8 | var globalCardCount = 0; 9 | var runningInference = false; 10 | 11 | function newCard() { 12 | if (runningInference) { 13 | console.log("Already running inference, please wait..."); 14 | setAlert("Already running inference, please wait..."); 15 | return; 16 | } 17 | var inputElement = document.getElementById("sentence-input"); 18 | var sentence = inputElement.value; 19 | if (sentence === "") { 20 | console.log("Please enter a sentence to analyze"); 21 | setAlert("Please enter a sentence to analyze"); 22 | return; 23 | } 24 | inputElement.value = ""; 25 | 26 | var cardIndex = globalCardCount; 27 | globalCardCount++; 28 | var newCard = document.createElement("div"); 29 | newCard.id = "card-" + cardIndex; 30 | newCard.innerHTML = ` 31 |
32 |
33 |
${sentence}
34 |
35 | 36 |
37 |
38 |
39 | `; 40 | document.getElementById("sentence-input").before(newCard); 41 | 42 | console.log("Running inference on sentence: " + sentence); 43 | runningInference = true; 44 | fetch("/api/sentiment-analysis", { 45 | method: "POST", 46 | headers: { 47 | "Content-Type": "application/json", 48 | }, 49 | body: JSON.stringify({ sentence: sentence }), 50 | }) 51 | .then((response) => response.json()) 52 | .then((data) => { 53 | console.log(data); 54 | updateCard(cardIndex, sentence, data.sentiment); 55 | }) 56 | .catch((error) => { 57 | console.log(error); 58 | }); 59 | } 60 | 61 | function updateCard(cardIndex, sentence, sentiment) { 62 | badge = ""; 63 | if (sentiment === "positive") { 64 | badge = `Positive`; 65 | } else if (sentiment === "negative") { 66 | badge = `Negative`; 67 | } else if (sentiment === "neutral") { 68 | badge = `Neutral`; 69 | } else { 70 | badge = `Unsure`; 71 | } 72 | var cardElement = document.getElementById("card-" + cardIndex); 73 | cardElement.innerHTML = ` 74 |
75 |
76 |
${sentence}
77 |
78 | ${badge} 79 |
80 |
81 |
82 | `; 83 | runningInference = false; 84 | } 85 | 86 | function setAlert(msg) { 87 | var alertElement = document.getElementById("alert"); 88 | alertElement.innerHTML = ` 89 |
90 | 91 | ${msg} 92 |
93 | `; 94 | setTimeout(function () { 95 | alertElement.innerHTML = ""; 96 | }, 3000); 97 | } 98 | -------------------------------------------------------------------------------- /sentiment-analysis-py/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sentiment Analyzer 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
25 | 35 |
36 |
37 |

38 | This Sentiment Analyzer is a demonstration of how you can use Fermyon 39 | Serverless AI to easily make an AI-powered API. When you type in a 40 | sentence it is sent to a Spin app running in the Fermyon Cloud, 41 | inferencing is performed using the Fermyon serverless AI feature, and 42 | the response is cached in a Fermyon key/value store. 43 |

44 |

45 | Note that LLM's are not perfect and the sentiment analysis performed 46 | by this application is not guaranteed to be perfect. 47 |

48 |

49 | To get started type a sentence below and press 50 | enter. 51 |

52 | 53 |
54 | 60 |
61 | 62 |
63 |
64 |
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /sentiment-analysis-py/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.0 2 | componentize-py == 0.16.0 3 | -------------------------------------------------------------------------------- /sentiment-analysis-py/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Mikkel Mørk Hegnhøj "] 5 | description = "A sentiment analysis API that demonstrates using LLM inferencing and KV stores together" 6 | name = "sentiment-analysis" 7 | version = "0.1.0" 8 | 9 | [[trigger.http]] 10 | route = "/api/..." 11 | component = "sentiment-analysis" 12 | 13 | [component.sentiment-analysis] 14 | source = "app.wasm" 15 | ai_models = ["llama2-chat"] 16 | key_value_stores = ["default"] 17 | [component.sentiment-analysis.build] 18 | command = "componentize-py -w spin-http componentize app -o app.wasm" 19 | watch = ["*.py", "requirements.txt"] 20 | 21 | [[trigger.http]] 22 | component = "kv-explorer" 23 | route = "/internal/kv-explorer/..." 24 | 25 | [component.kv-explorer] 26 | source = { url = "https://github.com/fermyon/spin-kv-explorer/releases/download/v0.10.0/spin-kv-explorer.wasm", digest = "sha256:65bc286f8315746d1beecd2430e178f539fa487ebf6520099daae09a35dbce1d" } 27 | allowed_outbound_hosts = ["redis://*:*", "mysql://*:*", "postgres://*:*"] 28 | # add or remove stores you want to explore here 29 | key_value_stores = ["default"] 30 | 31 | [component.kv-explorer.variables] 32 | kv_credentials = "{{ kv_explorer_user }}:{{ kv_explorer_password }}" 33 | 34 | [variables] 35 | kv_explorer_user = { required = true } 36 | kv_explorer_password = { required = true } 37 | 38 | [[trigger.http]] 39 | route = "/..." 40 | component = "ui" 41 | 42 | [component.ui] 43 | source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.3.0/spin_static_fs.wasm", digest = "sha256:ef88708817e107bf49985c7cefe4dd1f199bf26f6727819183d5c996baa3d148" } 44 | files = [{ source = "assets", destination = "/" }] 45 | -------------------------------------------------------------------------------- /sentiment-analysis-rs/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .spin/ 3 | -------------------------------------------------------------------------------- /sentiment-analysis-rs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.75" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.1.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "2.4.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 28 | 29 | [[package]] 30 | name = "bytes" 31 | version = "1.4.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 34 | 35 | [[package]] 36 | name = "fnv" 37 | version = "1.0.7" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 40 | 41 | [[package]] 42 | name = "form_urlencoded" 43 | version = "1.2.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 46 | dependencies = [ 47 | "percent-encoding", 48 | ] 49 | 50 | [[package]] 51 | name = "hashbrown" 52 | version = "0.12.3" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 55 | 56 | [[package]] 57 | name = "heck" 58 | version = "0.4.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 61 | dependencies = [ 62 | "unicode-segmentation", 63 | ] 64 | 65 | [[package]] 66 | name = "http" 67 | version = "0.2.9" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 70 | dependencies = [ 71 | "bytes", 72 | "fnv", 73 | "itoa", 74 | ] 75 | 76 | [[package]] 77 | name = "id-arena" 78 | version = "2.2.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 81 | 82 | [[package]] 83 | name = "idna" 84 | version = "0.4.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 87 | dependencies = [ 88 | "unicode-bidi", 89 | "unicode-normalization", 90 | ] 91 | 92 | [[package]] 93 | name = "indexmap" 94 | version = "1.9.3" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 97 | dependencies = [ 98 | "autocfg", 99 | "hashbrown", 100 | "serde", 101 | ] 102 | 103 | [[package]] 104 | name = "itoa" 105 | version = "1.0.9" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 108 | 109 | [[package]] 110 | name = "leb128" 111 | version = "0.2.5" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 114 | 115 | [[package]] 116 | name = "log" 117 | version = "0.4.20" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 120 | 121 | [[package]] 122 | name = "memchr" 123 | version = "2.6.2" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" 126 | 127 | [[package]] 128 | name = "percent-encoding" 129 | version = "2.3.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 132 | 133 | [[package]] 134 | name = "proc-macro2" 135 | version = "1.0.66" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 138 | dependencies = [ 139 | "unicode-ident", 140 | ] 141 | 142 | [[package]] 143 | name = "pulldown-cmark" 144 | version = "0.8.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" 147 | dependencies = [ 148 | "bitflags 1.3.2", 149 | "memchr", 150 | "unicase", 151 | ] 152 | 153 | [[package]] 154 | name = "quote" 155 | version = "1.0.33" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 158 | dependencies = [ 159 | "proc-macro2", 160 | ] 161 | 162 | [[package]] 163 | name = "routefinder" 164 | version = "0.5.3" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "94f8f99b10dedd317514253dda1fa7c14e344aac96e1f78149a64879ce282aca" 167 | dependencies = [ 168 | "smartcow", 169 | "smartstring", 170 | ] 171 | 172 | [[package]] 173 | name = "ryu" 174 | version = "1.0.15" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 177 | 178 | [[package]] 179 | name = "semver" 180 | version = "1.0.18" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" 183 | 184 | [[package]] 185 | name = "sentiment-analysis" 186 | version = "0.1.0" 187 | dependencies = [ 188 | "anyhow", 189 | "bytes", 190 | "http", 191 | "serde", 192 | "serde_json", 193 | "spin-sdk", 194 | ] 195 | 196 | [[package]] 197 | name = "serde" 198 | version = "1.0.188" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 201 | dependencies = [ 202 | "serde_derive", 203 | ] 204 | 205 | [[package]] 206 | name = "serde_derive" 207 | version = "1.0.188" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 210 | dependencies = [ 211 | "proc-macro2", 212 | "quote", 213 | "syn 2.0.29", 214 | ] 215 | 216 | [[package]] 217 | name = "serde_json" 218 | version = "1.0.105" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" 221 | dependencies = [ 222 | "itoa", 223 | "ryu", 224 | "serde", 225 | ] 226 | 227 | [[package]] 228 | name = "smartcow" 229 | version = "0.2.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "656fcb1c1fca8c4655372134ce87d8afdf5ec5949ebabe8d314be0141d8b5da2" 232 | dependencies = [ 233 | "smartstring", 234 | ] 235 | 236 | [[package]] 237 | name = "smartstring" 238 | version = "1.0.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" 241 | dependencies = [ 242 | "autocfg", 243 | "static_assertions", 244 | "version_check", 245 | ] 246 | 247 | [[package]] 248 | name = "spin-macro" 249 | version = "0.1.0" 250 | source = "git+https://github.com/fermyon/spin?tag=v1.5.0#ca08dd933de32fe09f4c318fbbc1f04853f2085f" 251 | dependencies = [ 252 | "anyhow", 253 | "bytes", 254 | "http", 255 | "proc-macro2", 256 | "quote", 257 | "syn 1.0.109", 258 | ] 259 | 260 | [[package]] 261 | name = "spin-sdk" 262 | version = "1.5.0" 263 | source = "git+https://github.com/fermyon/spin?tag=v1.5.0#ca08dd933de32fe09f4c318fbbc1f04853f2085f" 264 | dependencies = [ 265 | "anyhow", 266 | "bytes", 267 | "form_urlencoded", 268 | "http", 269 | "routefinder", 270 | "spin-macro", 271 | "thiserror", 272 | "wit-bindgen", 273 | ] 274 | 275 | [[package]] 276 | name = "static_assertions" 277 | version = "1.1.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 280 | 281 | [[package]] 282 | name = "syn" 283 | version = "1.0.109" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 286 | dependencies = [ 287 | "proc-macro2", 288 | "quote", 289 | "unicode-ident", 290 | ] 291 | 292 | [[package]] 293 | name = "syn" 294 | version = "2.0.29" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" 297 | dependencies = [ 298 | "proc-macro2", 299 | "quote", 300 | "unicode-ident", 301 | ] 302 | 303 | [[package]] 304 | name = "thiserror" 305 | version = "1.0.47" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" 308 | dependencies = [ 309 | "thiserror-impl", 310 | ] 311 | 312 | [[package]] 313 | name = "thiserror-impl" 314 | version = "1.0.47" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" 317 | dependencies = [ 318 | "proc-macro2", 319 | "quote", 320 | "syn 2.0.29", 321 | ] 322 | 323 | [[package]] 324 | name = "tinyvec" 325 | version = "1.6.0" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 328 | dependencies = [ 329 | "tinyvec_macros", 330 | ] 331 | 332 | [[package]] 333 | name = "tinyvec_macros" 334 | version = "0.1.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 337 | 338 | [[package]] 339 | name = "unicase" 340 | version = "2.7.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 343 | dependencies = [ 344 | "version_check", 345 | ] 346 | 347 | [[package]] 348 | name = "unicode-bidi" 349 | version = "0.3.13" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 352 | 353 | [[package]] 354 | name = "unicode-ident" 355 | version = "1.0.11" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 358 | 359 | [[package]] 360 | name = "unicode-normalization" 361 | version = "0.1.22" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 364 | dependencies = [ 365 | "tinyvec", 366 | ] 367 | 368 | [[package]] 369 | name = "unicode-segmentation" 370 | version = "1.10.1" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 373 | 374 | [[package]] 375 | name = "unicode-xid" 376 | version = "0.2.4" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 379 | 380 | [[package]] 381 | name = "url" 382 | version = "2.4.1" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 385 | dependencies = [ 386 | "form_urlencoded", 387 | "idna", 388 | "percent-encoding", 389 | ] 390 | 391 | [[package]] 392 | name = "version_check" 393 | version = "0.9.4" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 396 | 397 | [[package]] 398 | name = "wasm-encoder" 399 | version = "0.29.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" 402 | dependencies = [ 403 | "leb128", 404 | ] 405 | 406 | [[package]] 407 | name = "wasm-metadata" 408 | version = "0.8.0" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "36e5156581ff4a302405c44ca7c85347563ca431d15f1a773f12c9c7b9a6cdc9" 411 | dependencies = [ 412 | "anyhow", 413 | "indexmap", 414 | "serde", 415 | "wasm-encoder", 416 | "wasmparser", 417 | ] 418 | 419 | [[package]] 420 | name = "wasmparser" 421 | version = "0.107.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" 424 | dependencies = [ 425 | "indexmap", 426 | "semver", 427 | ] 428 | 429 | [[package]] 430 | name = "wit-bindgen" 431 | version = "0.8.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "392d16e9e46cc7ca98125bc288dd5e4db469efe8323d3e0dac815ca7f2398522" 434 | dependencies = [ 435 | "bitflags 2.4.0", 436 | "wit-bindgen-rust-macro", 437 | ] 438 | 439 | [[package]] 440 | name = "wit-bindgen-core" 441 | version = "0.8.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "d422d36cbd78caa0e18c3371628447807c66ee72466b69865ea7e33682598158" 444 | dependencies = [ 445 | "anyhow", 446 | "wit-component", 447 | "wit-parser", 448 | ] 449 | 450 | [[package]] 451 | name = "wit-bindgen-rust" 452 | version = "0.8.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "9b76db68264f5d2089dc4652581236d8e75c5b89338de6187716215fd0e68ba3" 455 | dependencies = [ 456 | "heck", 457 | "wasm-metadata", 458 | "wit-bindgen-core", 459 | "wit-bindgen-rust-lib", 460 | "wit-component", 461 | ] 462 | 463 | [[package]] 464 | name = "wit-bindgen-rust-lib" 465 | version = "0.8.0" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "1c50f334bc08b0903a43387f6eea6ef6aa9eb2a085729f1677b29992ecef20ba" 468 | dependencies = [ 469 | "heck", 470 | "wit-bindgen-core", 471 | ] 472 | 473 | [[package]] 474 | name = "wit-bindgen-rust-macro" 475 | version = "0.8.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "ced38a5e174940c6a41ae587babeadfd2e2c2dc32f3b6488bcdca0e8922cf3f3" 478 | dependencies = [ 479 | "anyhow", 480 | "proc-macro2", 481 | "syn 2.0.29", 482 | "wit-bindgen-core", 483 | "wit-bindgen-rust", 484 | "wit-component", 485 | ] 486 | 487 | [[package]] 488 | name = "wit-component" 489 | version = "0.11.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "7cbd4c7f8f400327c482c88571f373844b7889e61460650d650fc5881bb3575c" 492 | dependencies = [ 493 | "anyhow", 494 | "bitflags 1.3.2", 495 | "indexmap", 496 | "log", 497 | "wasm-encoder", 498 | "wasm-metadata", 499 | "wasmparser", 500 | "wit-parser", 501 | ] 502 | 503 | [[package]] 504 | name = "wit-parser" 505 | version = "0.8.0" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "6daec9f093dbaea0e94043eeb92ece327bbbe70c86b1f41aca9bbfefd7f050f0" 508 | dependencies = [ 509 | "anyhow", 510 | "id-arena", 511 | "indexmap", 512 | "log", 513 | "pulldown-cmark", 514 | "semver", 515 | "unicode-xid", 516 | "url", 517 | ] 518 | -------------------------------------------------------------------------------- /sentiment-analysis-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sentiment-analysis" 3 | authors = ["Rajat Jindal "] 4 | description = "A sentiment analysis API that demonstrates using LLM inference and KV stores together" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [lib] 9 | crate-type = [ "cdylib" ] 10 | 11 | [dependencies] 12 | # Useful crate to handle errors. 13 | anyhow = "1" 14 | # Crate to simplify working with bytes. 15 | bytes = "1" 16 | # General-purpose crate with common HTTP types. 17 | http = "0.2" 18 | serde = { version = "1.0", features = ["derive"] } 19 | serde_json = "1.0.85" 20 | 21 | # The Spin SDK. 22 | spin-sdk = { git = "https://github.com/fermyon/spin", tag = "v1.5.0" } 23 | 24 | [workspace] 25 | -------------------------------------------------------------------------------- /sentiment-analysis-rs/README.md: -------------------------------------------------------------------------------- 1 | # Sentiment Analysis 2 | 3 | This repository contains an API that performs sentiment analysis and a simple UI to interact with it. 4 | 5 | Please check the repo's [README](../README.md#prerequisites) for prerequisites for running this example. 6 | 7 | ## Build and Running 8 | 9 | ```bash 10 | $ spin build --up 11 | ``` 12 | 13 | > Note: If you are using the Cloud GPU component, remember to reference the `runtime-config.toml` file, e.g.: `spin build --up --runtime-config-file ./runtime-config.toml`. 14 | 15 | ## Test the Application 16 | 17 | ```bash 18 | $ curl --json '{"sentence": "Worst day ever"}' http://localhost:3000/api/sentiment-analysis 19 | ``` 20 | 21 | You can access the UI at http://localhost:3000. The KV-Explorer can be found at http://localhost:3000/internal/kv-explorer. 22 | 23 | ## Deploy the application to Fermyon Cloud 24 | 25 | ```bash 26 | $ cd api 27 | $ spin deploy 28 | ``` 29 | 30 | Make sure to change the url-reference in the `./client/src/main.rs`` file 31 | -------------------------------------------------------------------------------- /sentiment-analysis-rs/assets/dynamic.js: -------------------------------------------------------------------------------- 1 | // Listen for the Enter key being pressed 2 | document.addEventListener("keydown", function(event) { 3 | if (event.keyCode === 13) { 4 | newCard(); 5 | } 6 | }); 7 | 8 | var globalCardCount = 0; 9 | var runningInference = false; 10 | 11 | function newCard() { 12 | if (runningInference) { 13 | console.log("Already running inference, please wait..."); 14 | setAlert("Already running inference, please wait..."); 15 | return; 16 | } 17 | var inputElement = document.getElementById("sentence-input"); 18 | var sentence = inputElement.value; 19 | if (sentence === "") { 20 | console.log("Please enter a sentence to analyze"); 21 | setAlert("Please enter a sentence to analyze"); 22 | return; 23 | } 24 | inputElement.value = ""; 25 | 26 | var cardIndex = globalCardCount; 27 | globalCardCount++; 28 | var newCard = document.createElement("div"); 29 | newCard.id = "card-" + cardIndex; 30 | newCard.innerHTML = ` 31 |
32 |
33 |
${sentence}
34 |
35 | 36 |
37 |
38 |
39 | `; 40 | document.getElementById("sentence-input").before(newCard); 41 | 42 | console.log("Running inference on sentence: " + sentence); 43 | runningInference = true; 44 | fetch("/api/sentiment-analysis", { 45 | method: "POST", 46 | headers: { 47 | "Content-Type": "application/json", 48 | }, 49 | body: JSON.stringify({ sentence: sentence }), 50 | }) 51 | .then((response) => response.json()) 52 | .then((data) => { 53 | console.log(data); 54 | updateCard(cardIndex, sentence, data.sentiment); 55 | }) 56 | .catch((error) => { 57 | console.log(error); 58 | }); 59 | } 60 | 61 | function updateCard(cardIndex, sentence, sentiment) { 62 | badge = ""; 63 | if (sentiment === "positive") { 64 | badge = `Positive`; 65 | } else if (sentiment === "negative") { 66 | badge = `Negative`; 67 | } else if (sentiment === "neutral") { 68 | badge = `Neutral`; 69 | } else { 70 | badge = `Unsure`; 71 | } 72 | var cardElement = document.getElementById("card-" + cardIndex); 73 | cardElement.innerHTML = ` 74 |
75 |
76 |
${sentence}
77 |
78 | ${badge} 79 |
80 |
81 |
82 | `; 83 | runningInference = false; 84 | } 85 | 86 | function setAlert(msg) { 87 | var alertElement = document.getElementById("alert"); 88 | alertElement.innerHTML = ` 89 |
90 | 91 | ${msg} 92 |
93 | `; 94 | setTimeout(function() { 95 | alertElement.innerHTML = ""; 96 | }, 3000); 97 | } 98 | -------------------------------------------------------------------------------- /sentiment-analysis-rs/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sentiment Analyzer 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
25 | 35 |
36 |
37 |

38 | This Sentiment Analyzer is a demonstration of how you can use Fermyon 39 | Serverless AI to easily make an AI-powered API. When you type in a 40 | sentence it is sent to a Spin app running in the Fermyon Cloud, 41 | inferencing is performed using the Fermyon serverless AI feature, and 42 | the response is cached in a Fermyon key/value store. 43 |

44 |

45 | Note that LLM's are not perfect and the sentiment analysis performed 46 | by this application is not guaranteed to be perfect. 47 |

48 |

49 | To get started type a sentence below and press 50 | enter. 51 |

52 | 53 |
54 | 60 |
61 | 62 |
63 |
64 |
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /sentiment-analysis-rs/spin.toml: -------------------------------------------------------------------------------- 1 | spin_version = "1" 2 | authors = ["Rajat Jindal "] 3 | description = "A sentiment analysis API that demonstrates using LLM inference and KV stores together" 4 | name = "sentiment-analysis" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "sentiment-analysis" 10 | source = "target/wasm32-wasi/release/sentiment_analysis.wasm" 11 | key_value_stores = ["default"] 12 | ai_models = ["llama2-chat"] 13 | [component.trigger] 14 | route = "/api/..." 15 | [component.build] 16 | command = "cargo build --target wasm32-wasi --release" 17 | watch = ["src/**/*.rs", "Cargo.toml"] 18 | 19 | [[component]] 20 | source = { url = "https://github.com/radu-matei/spin-kv-explorer/releases/download/v0.8.0/spin-kv-explorer.wasm", digest = "sha256:e1667e756004000913d869b72db600fb2675f4358c6f0cc2581dfa869e56073c" } 21 | id = "kv-explorer" 22 | # add or remove stores you want to explore here 23 | key_value_stores = ["default"] 24 | [component.trigger] 25 | route = "/internal/kv-explorer/..." 26 | 27 | [[component]] 28 | source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.0.1/spin_static_fs.wasm", digest = "sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375" } 29 | id = "ui" 30 | files = [{ source = "../sentiment-analysis-assets", destination = "/" }] 31 | [component.trigger] 32 | route = "/..." 33 | -------------------------------------------------------------------------------- /sentiment-analysis-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use anyhow::Result; 4 | use spin_sdk::{ 5 | http::{Params, Request, Response, Router}, 6 | http_component, 7 | key_value::Store, 8 | llm::{infer_with_options, InferencingModel::Llama2Chat}, 9 | }; 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Deserialize)] 14 | pub struct SentimentAnalysisRequest { 15 | pub sentence: String, 16 | } 17 | 18 | #[derive(Serialize)] 19 | pub struct SentimentAnalysisResponse { 20 | pub sentiment: String, 21 | } 22 | 23 | const PROMPT: &str = r#" 24 | <> 25 | You are a bot that generates sentiment analysis responses. Respond with a single positive, negative, or neutral. 26 | <> 27 | [INST] 28 | Follow the pattern of the following examples: 29 | 30 | User: Hi, my name is Bob 31 | Bot: neutral 32 | 33 | User: I am so happy today 34 | Bot: positive 35 | 36 | User: I am so sad today 37 | Bot: negative 38 | [/INST] 39 | 40 | User: {SENTENCE} 41 | "#; 42 | 43 | /// A Spin HTTP component that internally routes requests. 44 | #[http_component] 45 | fn handle_route(req: Request) -> Result { 46 | let mut router = Router::new(); 47 | router.post("/api/sentiment-analysis", perform_sentiment_analysis); 48 | router.any("/api/*", not_found); 49 | router.handle(req) 50 | } 51 | 52 | fn not_found(_: Request, _: Params) -> Result { 53 | Ok(http::Response::builder() 54 | .status(404) 55 | .body(Some("Not found".into()))?) 56 | } 57 | 58 | fn perform_sentiment_analysis(req: Request, _params: Params) -> Result { 59 | let request = body_json_to_map(&req)?; 60 | // Do some basic clean up on the input 61 | let sentence = request.sentence.trim(); 62 | println!("Performing sentiment analysis on: {}", sentence); 63 | 64 | // Prepare the KV store 65 | let kv = Store::open_default()?; 66 | 67 | // If the sentiment of the sentence is already in the KV store, return it 68 | if let Ok(sentiment) = kv.get(sentence) { 69 | println!("Found sentence in KV store returning cached sentiment"); 70 | let resp = SentimentAnalysisResponse { 71 | sentiment: String::from_utf8(sentiment)?, 72 | }; 73 | 74 | return send_ok_response(200, resp); 75 | } 76 | println!("Sentence not found in KV store"); 77 | 78 | // Otherwise, perform sentiment analysis 79 | println!("Running inference"); 80 | let inferencing_result = infer_with_options( 81 | Llama2Chat, 82 | &PROMPT.replace("{SENTENCE}", sentence), 83 | spin_sdk::llm::InferencingParams { 84 | max_tokens: 6, 85 | ..Default::default() 86 | }, 87 | )?; 88 | println!("Inference result {:?}", inferencing_result); 89 | let sentiment = inferencing_result 90 | .text 91 | .lines() 92 | .next() 93 | .unwrap_or_default() 94 | .strip_prefix("Bot:") 95 | .unwrap_or_default() 96 | .parse::(); 97 | println!("Got sentiment: {sentiment:?}"); 98 | 99 | if let Ok(sentiment) = sentiment { 100 | println!("Caching sentiment in KV store"); 101 | let _ = kv.set(sentence, sentiment); 102 | } 103 | // Cache the result in the KV store 104 | let resp = SentimentAnalysisResponse { 105 | sentiment: sentiment 106 | .as_ref() 107 | .map(ToString::to_string) 108 | .unwrap_or_default(), 109 | }; 110 | 111 | send_ok_response(200, resp) 112 | } 113 | 114 | fn send_ok_response(code: u16, resp: SentimentAnalysisResponse) -> Result { 115 | Ok(http::Response::builder() 116 | .status(code) 117 | .body(Some(serde_json::to_string(&resp)?.into()))?) 118 | } 119 | 120 | fn body_json_to_map(req: &Request) -> Result { 121 | let body = match req.body().as_ref() { 122 | Some(bytes) => bytes, 123 | None => anyhow::bail!("Request body was unexpectedly empty"), 124 | }; 125 | 126 | Ok(serde_json::from_slice(&body)?) 127 | } 128 | 129 | #[derive(Copy, Clone, Debug)] 130 | enum Sentiment { 131 | Positive, 132 | Negative, 133 | Neutral, 134 | } 135 | 136 | impl Sentiment { 137 | fn as_str(&self) -> &str { 138 | match self { 139 | Self::Positive => "positive", 140 | Self::Negative => "negative", 141 | Self::Neutral => "neutral", 142 | } 143 | } 144 | } 145 | 146 | impl std::fmt::Display for Sentiment { 147 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 148 | f.write_str(self.as_str()) 149 | } 150 | } 151 | 152 | impl AsRef<[u8]> for Sentiment { 153 | fn as_ref(&self) -> &[u8] { 154 | self.as_str().as_bytes() 155 | } 156 | } 157 | 158 | impl FromStr for Sentiment { 159 | type Err = String; 160 | 161 | fn from_str(s: &str) -> std::result::Result { 162 | let sentiment = match s.trim() { 163 | "positive" => Self::Positive, 164 | "negative" => Self::Negative, 165 | "neutral" => Self::Neutral, 166 | _ => return Err(s.into()), 167 | }; 168 | Ok(sentiment) 169 | } 170 | } -------------------------------------------------------------------------------- /sentiment-analysis-ts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | target 4 | .spin/ -------------------------------------------------------------------------------- /sentiment-analysis-ts/README.md: -------------------------------------------------------------------------------- 1 | # Sentiment Analysis 2 | 3 | This repository contains an API that performs sentiment analysis and a simple UI to interact with it. 4 | 5 | Please check the repo's [README](../README.md#prerequisites) for prerequisites for running this example. 6 | 7 | ## Build and Running 8 | 9 | Install dependencies, build, and run your application. To avoid configuring secrets for the [KV explorer](https://developer.fermyon.com/hub/preview/template_kv_explorer), set an environment variable to skip auth. 10 | ```bash 11 | $ npm install 12 | $ spin build --up --env SPIN_APP_KV_SKIP_AUTH=1 13 | ``` 14 | 15 | > Note: If you are using the Cloud GPU component, remember to reference the `runtime-config.toml` file, e.g.: `spin build --up --env SPIN_APP_KV_SKIP_AUTH=1 --runtime-config-file ./runtime-config.toml`. 16 | 17 | ## Test the Application 18 | 19 | ```bash 20 | $ curl --json '{"sentence": "Worst day ever"}' http://localhost:3000/api/sentiment-analysis 21 | ``` 22 | 23 | You can access the UI at http://localhost:3000. The KV-Explorer can be found at http://localhost:3000/internal/kv-explorer. 24 | 25 | ## Deploy the application to Fermyon Cloud 26 | 27 | ```bash 28 | $ cd api 29 | $ spin deploy 30 | ``` 31 | -------------------------------------------------------------------------------- /sentiment-analysis-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sentiment-analysis", 3 | "version": "1.0.0", 4 | "description": "A sentiment analysis API that demonstrates using LLM inference and KV stores together", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npx webpack --mode=production && mkdir -p target && spin js2wasm -o target/spin-http-js.wasm dist/spin.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@fermyon/spin-sdk": "0.6.0-rc.0", 15 | "ts-loader": "^9.4.1", 16 | "typescript": "^4.8.4", 17 | "webpack": "^5.74.0", 18 | "webpack-cli": "^4.10.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sentiment-analysis-ts/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "sentiment-analysis" 5 | version = "0.1.0" 6 | authors = ["Caleb Schoepp "] 7 | description = "A sentiment analysis API that demonstrates using LLM inference and KV stores together" 8 | 9 | [[trigger.http]] 10 | route = "/api/..." 11 | component = "sentiment-analysis" 12 | 13 | [component.sentiment-analysis] 14 | source = "target/spin-http-js.wasm" 15 | allowed_outbound_hosts = [] 16 | exclude_files = ["**/node_modules"] 17 | key_value_stores = ["default"] 18 | ai_models = ["llama2-chat"] 19 | 20 | [component.sentiment-analysis.build] 21 | command = "npm run build" 22 | watch = ["src/**/*", "package.json", "package-lock.json"] 23 | 24 | [[trigger.http]] 25 | route = "/..." 26 | component = "ui" 27 | 28 | [component.ui] 29 | source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.0.1/spin_static_fs.wasm", digest = "sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375" } 30 | allowed_outbound_hosts = [] 31 | files = [{ source = "../sentiment-analysis-assets", destination = "/" }] 32 | 33 | [variables] 34 | kv_explorer_user = { required = true } 35 | kv_explorer_password = { required = true } 36 | 37 | [[trigger.http]] 38 | component = "kv-explorer" 39 | route = "/internal/kv-explorer/..." 40 | 41 | [component.kv-explorer] 42 | source = { url = "https://github.com/fermyon/spin-kv-explorer/releases/download/v0.10.0/spin-kv-explorer.wasm", digest = "sha256:65bc286f8315746d1beecd2430e178f539fa487ebf6520099daae09a35dbce1d" } 43 | allowed_outbound_hosts = ["redis://*:*", "mysql://*:*", "postgres://*:*"] 44 | # add or remove stores you want to explore here 45 | key_value_stores = ["default"] 46 | 47 | [component.kv-explorer.variables] 48 | kv_credentials = "{{ kv_explorer_user }}:{{ kv_explorer_password }}" -------------------------------------------------------------------------------- /sentiment-analysis-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HandleRequest, 3 | HttpRequest, 4 | HttpResponse, 5 | Llm, 6 | InferencingModels, 7 | InferencingOptions, 8 | Router, 9 | Kv, 10 | } from "@fermyon/spin-sdk"; 11 | 12 | interface SentimentAnalysisRequest { 13 | sentence: string; 14 | } 15 | 16 | interface SentimentAnalysisResponse { 17 | sentiment: "negative" | "neutral" | "positive"; 18 | } 19 | 20 | const decoder = new TextDecoder(); 21 | 22 | const PROMPT = `\ 23 | <> 24 | You are a bot that generates sentiment analysis responses. Respond with a single positive, negative, or neutral. 25 | <> 26 | [INST] 27 | Follow the pattern of the following examples: 28 | 29 | User: Hi, my name is Bob 30 | Bot: neutral 31 | 32 | User: I am so happy today 33 | Bot: positive 34 | 35 | User: I am so sad today 36 | Bot: negative 37 | [/INST] 38 | 39 | User: {SENTENCE} 40 | `; 41 | 42 | async function performSentimentAnalysis(request: HttpRequest) { 43 | // Parse sentence out of request 44 | let data = request.json() as SentimentAnalysisRequest; 45 | let sentence = data.sentence.trim(); 46 | console.log("Performing sentiment analysis on: " + sentence); 47 | 48 | // Prepare the KV store 49 | let kv = Kv.openDefault(); 50 | 51 | // If the sentiment of the sentence is already in the KV store, return it 52 | let cachedSentiment = kv.get(sentence); 53 | if (cachedSentiment !== null) { 54 | console.log("Found sentence in KV store returning cached sentiment"); 55 | return { 56 | status: 200, 57 | body: JSON.stringify({ 58 | sentiment: decoder.decode(cachedSentiment), 59 | } as SentimentAnalysisResponse), 60 | }; 61 | } 62 | console.log("Sentence not found in KV store"); 63 | 64 | // Otherwise, perform sentiment analysis 65 | console.log("Running inference"); 66 | let options: InferencingOptions = { maxTokens: 6 }; 67 | let inferenceResult = Llm.infer( 68 | InferencingModels.Llama2Chat, 69 | PROMPT.replace("{SENTENCE}", sentence), 70 | options 71 | ); 72 | console.log( 73 | `Inference result (${inferenceResult.usage.generatedTokenCount} tokens): ${inferenceResult.text}` 74 | ); 75 | let sentiment = inferenceResult.text.split(/\s+/)[1]?.trim(); 76 | 77 | // Clean up result from inference 78 | if ( 79 | sentiment === undefined || 80 | !["negative", "neutral", "positive"].includes(sentiment) 81 | ) { 82 | sentiment = "unsure"; 83 | console.log("Invalid sentiment, marking it as unsure"); 84 | } 85 | 86 | // Cache the result in the KV store 87 | if (sentiment !== "unsure") { 88 | console.log("Caching sentiment in KV store"); 89 | kv.set(sentence, sentiment); 90 | } 91 | 92 | return { 93 | status: 200, 94 | body: JSON.stringify({ 95 | sentiment, 96 | } as SentimentAnalysisResponse), 97 | }; 98 | } 99 | 100 | let router = Router(); 101 | 102 | // Map the route to the handler 103 | router.post("/api/sentiment-analysis", async (_, req) => { 104 | console.log(`${new Date().toISOString()} POST /sentiment-analysis`); 105 | return await performSentimentAnalysis(req); 106 | }); 107 | 108 | // Catch all 404 handler 109 | router.all("/api/*", async (_, _req) => { 110 | return { 111 | status: 404, 112 | body: "Not found", 113 | }; 114 | }); 115 | 116 | // Entry point to the Spin handler 117 | export const handleRequest: HandleRequest = async function( 118 | request: HttpRequest 119 | ): Promise { 120 | return await router.handleRequest(request, request); 121 | }; -------------------------------------------------------------------------------- /sentiment-analysis-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "module": "es6", 6 | "target": "es5", 7 | "jsx": "react", 8 | "skipLibCheck": true, 9 | "lib": ["ES2015"], 10 | "allowJs": true, 11 | "strict": true, 12 | "noImplicitReturns": true, 13 | "moduleResolution": "node" 14 | } 15 | } -------------------------------------------------------------------------------- /sentiment-analysis-ts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.tsx?$/, 9 | use: 'ts-loader', 10 | exclude: /node_modules/, 11 | }, 12 | ], 13 | }, 14 | resolve: { 15 | extensions: ['.tsx', '.ts', '.js'], 16 | }, 17 | output: { 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'spin.js', 20 | library: 'spin' 21 | }, 22 | optimization: { 23 | minimize: false 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /silly-walk-ts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | target 4 | .spin/ -------------------------------------------------------------------------------- /silly-walk-ts/README.md: -------------------------------------------------------------------------------- 1 | ## Silly Walk (TypeScript) 2 | 3 | This is a minimal example of using the Fermyon Serverless AI inside of your serverless function. 4 | 5 | The code is in `src/index.ts` 6 | 7 | Please check the repo's [README](../README.md#prerequisites) for prerequisites for running this example. 8 | 9 | ## Building and Running 10 | 11 | ```bash 12 | $ npm install 13 | $ spin build --up 14 | ``` 15 | 16 | > Note: If you are using the Cloud GPU component, remember to reference the `runtime-config.toml` file, e.g.: `spin build --up --runtime-config-file ./runtime-config.toml`. 17 | 18 | ## Deploy the application to Fermyon Cloud 19 | 20 | ```bash 21 | $ cd api 22 | $ spin deploy 23 | ``` 24 | -------------------------------------------------------------------------------- /silly-walk-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "silly-walk", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npx webpack --mode=production && mkdir -p target && spin js2wasm -o target/silly-walk.wasm dist/spin.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "ts-loader": "^9.4.1", 15 | "typescript": "^4.8.4", 16 | "webpack": "^5.74.0", 17 | "webpack-cli": "^4.10.0" 18 | }, 19 | "dependencies": { 20 | "@fermyon/spin-sdk": "0.6.0-rc.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /silly-walk-ts/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = "1" 2 | authors = ["Matt Butcher "] 3 | description = "" 4 | name = "silly-walk" 5 | trigger = { type = "http", base = "/" } 6 | version = "0.1.0" 7 | 8 | [[component]] 9 | id = "silly-walk" 10 | source = "target/silly-walk.wasm" 11 | exclude_files = ["**/node_modules"] 12 | ai_models = ["llama2-chat"] 13 | [component.trigger] 14 | route = "/..." 15 | [component.build] 16 | command = "npm run build" 17 | -------------------------------------------------------------------------------- /silly-walk-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Llm, InferencingModels, HandleRequest, HttpRequest, HttpResponse } from "@fermyon/spin-sdk" 2 | 3 | const model = InferencingModels.Llama2Chat 4 | 5 | export const handleRequest: HandleRequest = async function (request: HttpRequest): Promise { 6 | const prompt = "As a monty python character explain how to walk. Limit to 3 sentences." 7 | const out = Llm.infer(model, prompt, { maxTokens: 200 }) 8 | return { 9 | status: 200, 10 | body: out.text 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /silly-walk-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "module": "es6", 6 | "target": "es5", 7 | "jsx": "react", 8 | "skipLibCheck": true, 9 | "lib": ["ES2015"], 10 | "allowJs": true, 11 | "strict": true, 12 | "noImplicitReturns": true, 13 | "moduleResolution": "node" 14 | } 15 | } -------------------------------------------------------------------------------- /silly-walk-ts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.tsx?$/, 9 | use: 'ts-loader', 10 | exclude: /node_modules/, 11 | }, 12 | ], 13 | }, 14 | resolve: { 15 | extensions: ['.tsx', '.ts', '.js'], 16 | }, 17 | output: { 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'spin.js', 20 | library: 'spin' 21 | }, 22 | optimization: { 23 | minimize: false 24 | }, 25 | }; 26 | --------------------------------------------------------------------------------