From f66ad340a68e1744cf828faadc567a7ed40c0e44 Mon Sep 17 00:00:00 2001 From: Adam Getchell Date: Fri, 12 Jul 2024 10:03:56 -0700 Subject: [PATCH 1/2] Update ai-sentiment-analysis-api-tutorial.md This version compiles and has basic tests. Signed-off-by: Adam Getchell --- .../v2/ai-sentiment-analysis-api-tutorial.md | 73 ++++++++++++++----- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/content/spin/v2/ai-sentiment-analysis-api-tutorial.md b/content/spin/v2/ai-sentiment-analysis-api-tutorial.md index 898210de3..75daa5951 100644 --- a/content/spin/v2/ai-sentiment-analysis-api-tutorial.md +++ b/content/spin/v2/ai-sentiment-analysis-api-tutorial.md @@ -301,7 +301,7 @@ use std::str::FromStr; use anyhow::Result; use spin_sdk::{ - http::{IntoResponse, Json, Params, Request, Response, Router}, + http::{IntoResponse, Params, Request, Response, Router}, http_component, key_value::Store, llm::{infer_with_options, InferencingModel::Llama2Chat}, @@ -352,32 +352,28 @@ fn not_found(_: Request, _: Params) -> Result { Ok(Response::new(404, "Not found")) } -fn perform_sentiment_analysis( - req: http::Request>, - _params: Params, -) -> Result { - // Do some basic clean up on the input - let sentence = req.body().sentence.trim(); +fn perform_sentiment_analysis(req: Request, _params: Params) -> Result { + let request = body_json_to_map(&req)?; + // Do some basic cleanup on the input + let sentence = request.sentence.trim(); println!("Performing sentiment analysis on: {}", sentence); // Prepare the KV store let kv = Store::open_default()?; // If the sentiment of the sentence is already in the KV store, return it - if kv.exists(sentence).unwrap_or(false) { - println!("Found sentence in KV store returning cached sentiment"); - let sentiment = kv.get(sentence)?; + if let Ok(sentiment) = kv.get(sentence) { + println!("Found sentence in KV store returning cached sentiment."); let resp = SentimentAnalysisResponse { sentiment: String::from_utf8(sentiment.unwrap())?, }; - let resp_str = serde_json::to_string(&resp)?; - return Ok(Response::new(200, resp_str)); + return send_ok_response(200, resp); } - println!("Sentence not found in KV store"); + println!("Sentence not found in KV store."); - // Otherwise, perform sentiment analysis - println!("Running inference"); + // Perform sentiment analysis + println!("Running inference..."); let inferencing_result = infer_with_options( Llama2Chat, &PROMPT.replace("{SENTENCE}", sentence), @@ -387,7 +383,7 @@ fn perform_sentiment_analysis( }, )?; - println!("Inference result {:?}", inferencing_result); + println!("Inference result: {:?}", inferencing_result); let sentiment = inferencing_result .text @@ -400,11 +396,11 @@ fn perform_sentiment_analysis( println!("Got sentiment: {sentiment:?}"); if let Ok(sentiment) = sentiment { - println!("Caching sentiment in KV store"); + println!("Caching sentiment in KV store."); let _ = kv.set(sentence, sentiment.as_str().as_bytes()); } - // Cache the result in the KV store + // Cache result in KV store let resp = SentimentAnalysisResponse { sentiment: sentiment .as_ref() @@ -412,9 +408,18 @@ fn perform_sentiment_analysis( .unwrap_or_default(), }; + send_ok_response(200, resp) +} + +fn send_ok_response(code: u16, resp: SentimentAnalysisResponse) -> Result { let resp_str = serde_json::to_string(&resp)?; + Ok(Response::new(code, resp_str)) +} + +fn body_json_to_map(req: &Request) -> Result { + let body = String::from_utf8(req.body().as_ref().to_vec())?; - Ok(Response::new(200, resp_str)) + Ok(SentimentAnalysisRequest { sentence: body }) } #[derive(Copy, Clone, Debug)] @@ -440,6 +445,12 @@ impl std::fmt::Display for Sentiment { } } +impl AsRef<[u8]> for Sentiment { + fn as_ref(&self) -> &[u8] { + self.as_str().as_bytes() + } +} + impl FromStr for Sentiment { type Err = String; @@ -448,11 +459,33 @@ impl FromStr for Sentiment { "positive" => Self::Positive, "negative" => Self::Negative, "neutral" => Self::Neutral, - _ => return Err(s.into()), + _ => return Err(format!("Invalid sentiment: {}", s)), }; Ok(sentiment) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize_sentiment_analysis_request() { + let json = r#"{"sentence":"I am so happy today"}"#; + let request: SentimentAnalysisRequest = serde_json::from_str(json).unwrap(); + assert_eq!(request.sentence, "I am so happy today"); + } + + #[test] + fn serialize_sentiment_analysis_response() { + let response = SentimentAnalysisResponse { + sentiment: "positive".to_string(), + }; + let json = serde_json::to_string(&response).unwrap(); + assert_eq!(json, r#"{"sentiment":"positive"}"#); + } +} + ``` {{ blockEnd }} From 6ab4fc39658e5a6c8a28059cc848f9fac9061652 Mon Sep 17 00:00:00 2001 From: Adam Getchell Date: Fri, 12 Jul 2024 12:04:12 -0700 Subject: [PATCH 2/2] Update ai-sentiment-analysis-api-tutorial.md Signed-off-by: Adam Getchell --- .../spin/v2/ai-sentiment-analysis-api-tutorial.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/content/spin/v2/ai-sentiment-analysis-api-tutorial.md b/content/spin/v2/ai-sentiment-analysis-api-tutorial.md index 75daa5951..37082a95f 100644 --- a/content/spin/v2/ai-sentiment-analysis-api-tutorial.md +++ b/content/spin/v2/ai-sentiment-analysis-api-tutorial.md @@ -353,7 +353,9 @@ fn not_found(_: Request, _: Params) -> Result { } fn perform_sentiment_analysis(req: Request, _params: Params) -> Result { - let request = body_json_to_map(&req)?; + let Ok(request) = serde_json::from_slice::(req.body()) else { + return Ok(Response::new(400, "Bad Request")); + }; // Do some basic cleanup on the input let sentence = request.sentence.trim(); println!("Performing sentiment analysis on: {}", sentence); @@ -362,10 +364,10 @@ fn perform_sentiment_analysis(req: Request, _params: Params) -> Result Result Result { - let body = String::from_utf8(req.body().as_ref().to_vec())?; - - Ok(SentimentAnalysisRequest { sentence: body }) -} - #[derive(Copy, Clone, Debug)] enum Sentiment { Positive,