Ivan Cernja
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    // Comments for Joshie 1. Could you please upload the code to the shuttle-hq/x repo? See maybe with Engineering where we can host these tutorial-related apps/projects and then update the part where it says: "The full code of the Rust AI helper can be found here"? 2. Could you please upload the gif when you open the PR and make sure that it's there/works? Here's the link to it: https://gifyu.com/image/SZPrW 3. Could you make a sexy thumbnail? 4. Lmk your thoughts on the article and the action points above. # Building Your First AI Tool in Rust In this tutorial, we'll build a command-line application in Rust that can analyze data from a CSV file. The app will prompt the user to enter a question, and it will provide an answer based on the data in the CSV file. At the end, we'll be able to ask our helper various questions, and get various answers! Here's a short video that shows its powers: ![](https://s7.ezgif.com/tmp/ezgif-7-69f254d3cf.gif) The full code of the Rust AI helper can be found here. ## Prerequisites Before we get started, make sure you have the following installed: * Rust (you can install it from [rustup.rs](https://rustup.rs)) * The `csv` and `llm_chain` crates (we'll install these later) You'll also need an OpenAI API key which you can [get here](https://platform.openai.com/api-keys). ## Setting up the Project First, create a new Rust project: ```bash cargo new data-analysis-app cd data-analysis-app ``` Next, open the `Cargo.toml` file and add the required dependencies: ```toml [dependencies] csv = "1.1" dotenvy = "0.15.7" llm-chain = "0.13.0" llm-chain-openai - "0.13.0" tokio = { version = "1.25.0", features = ["macros", "rt-multi-thread"] } ``` These lines add the required dependencies for our project: `csv` for reading CSV files, [`llm_chain`](https://github.com/sobelio/llm-chain) for natural language processing, and [`tokio`](https://tokio.rs) for async runtime functionality. We use `dotenvy` for `.env` file handling as well as `llm-chain-openai` (for integration with the OpenAI API). ## What is llm-chain? Here is the description from their [official GitHub repository](https://github.com/sobelio/llm-chain): > llm-chain is a collection of Rust crates designed to help you create advanced LLM applications such as chatbots, agents, and more. `llm-chain` is effectively an LLM orchestration library that allows you to "chain" prompts together, executing one after the other - with extra features: - Templates are supported, allowing you to not need to manually chain steps - We can carry out complex tasks that LLMs cannot handle in a single step - `llm-chain` also supports vector storage, allowing long term memory and subject matter knowledge. This benefits us in several ways: - Being able to chain steps allows us to get much closer to a useful output, rather than only being able to use one step. For example, you may want several pre-instruction prompts to set up a context for your application. - By operating on data in steps, we can get much better insight from our data. LLM orchestration is a crucial tool for enterprise AI, as business use cases often require advanced contexts. ## Diving into it Let's update our `main.rs` file by importing all the neccessary crates we will need. I've added comments to the snippet below that describe what each crate/module is used for. ```rust use std::env; use std::error::Error; use std::fs::File; use std::io::{self, Write}; use csv::Reader; use llm_chain::{executor, parameters, prompt, step::Step}; ``` Next, we will mark our `main` function with the `async` keyword and add the `#[tokio::main]` macro right on-top of it. This allows us to use the Tokio asynchronous runtime for async functionality and automatic polling of futures (values that have may or may not finished work). P.S. If you'd like to learn more about Async in Rust, you can check out our [Async Rust in a Nutshell](https://www.shuttle.rs/blog/2024/02/29/async-rust) article! ```rust #[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { // ... } ``` The return type is set to `Result<(), Box<dyn Error>>` to enable error propagation with `?` and overall error handling across different error types. ### Adding our API Key Instead of hardcoding the API key in our code, we'll store it in a `.env` file, which is a more secure and convenient approach. Quickly create a new file called `.env` in the root directory of your project, open it and add the following line to it: `OPENAI_API_KEY=your_api_key_here` Make sure to replace `your_api_key_here` with your actual API key which you can [get here](https://platform.openai.com/api-keys). Next, we'll add one more crate and that is the `dotenv` crate which will allow us to load the environment variables from the `.env` file. Simply run the `cargo install dotenv` command and you should be good to go. In your `main.rs` file, import the `dotenvy` crate at the top of the file: ```rust use dotenvy::dotenv; ``` And finally, in your `main.rs` file, before any other code in the main function, call the `dotenv` function to load the environment variables from the `.env` file, like so: ```rust #[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { dotenv().ok(); // ... (rest of your code) } ``` By following this approach, we can keep the API key separate from our code, making it easier to manage and share our project without exposing any sensitive information. ### Creating an Executor We create an executor instance using the `executor!` macro from the `llm_chain` crate. The macro makes it easy for us to create a new executor for a specific model without having to directly call the constructor functions of the respective executor structs. In short; it allows you to call an LLM with a pre-defined input and output, using multiple steps to refine the output. ```rust let exec = executor!()?; ``` ### Reading the CSV file For this example, we'll be reading data from a CSV file because we are building a data-helper tool which will allows us to ask it various questions regarding the data at-hand. The snippet below opens a CSV file named "data.csv" in the root folder and reads its contents into a string variable `csv_data`, where each row is represented as a comma-separated string with a newline character at the end. It also uses the `csv` crate to handle the CSV parsing. In short, it makes sure that we can the `csv` data for further actions. ```rust let file = File::open("data.csv")?; let mut reader = Reader::from_reader(file); let mut csv_data = String::new(); for result in reader.records() { let record = result?; csv_data.push_str(&record.iter().collect::<Vec<_>>().join(",")); csv_data.push('\n'); } ``` The contents of the CSV file (make sure to create a `data.csv` file in your root directory and copy-paste the contents below): ```csv Name,Age,Occupation,City,FavoriteSport,AnnualIncome Samantha,28,Entrepreneur,New York,Skydiving,$120000 Michael,35,Software Engineer,San Francisco,Rock Climbing,$95000 Emily,42,Chef,Chicago,Surfing,$65000 David,25,Artist,Los Angeles,Parkour,$30000 Sophia,31,Pilot,Miami,Bungee Jumping,$85000 Daniel,47,Doctor,Boston,Snowboarding,$180000 Olivia,22,Student,Seattle,Skateboarding,$12000 William,39,Marketing Manager,Austin,Mountain Biking,$110000 Ava,27,Photographer,Portland,Kayaking,$45000 Jacob,33,Teacher,Denver,Hiking,$55000 Isabella,40,Lawyer,Washington D.C.,Scuba Diving,$200000 Ethan,29,Musician,Nashville,Bouldering,$25000 Mia,36,Graphic Designer,Atlanta,Skiing,$75000 Benjamin,44,Engineer,Houston,Surfing,$125000 Abigail,23,Writer,Minneapolis,Rock Climbing,$18000 ``` Example of what it looks like as a table: |Name |Age|Occupation |City |FavoriteSport |AnnualIncome| |--------|---|-----------------|---------------|---------------|------------| |Samantha|28 |Entrepreneur |New York |Skydiving |$120000 | |Michael |35 |Software Engineer|San Francisco |Rock Climbing |$95000 | |Emily |42 |Chef |Chicago |Surfing |$65000 | |David |25 |Artist |Los Angeles |Parkour |$30000 | ### Creating the user input loop The user input loop is the loop which the user uses to continuosly ask questions to our helper. The next couple of sections are all happening within this loop -- prompting, executing, outputting the result, etcetera. To start thing off; we'll be asking the user to enter their question and when the user is done with asking questions, they can type in `quit` to exit the helper. ```rust loop { println!("Enter your prompt (or 'quit' to exit):"); io::stdout().flush()?; let mut user_prompt = String::new(); io::stdin().read_line(&mut user_prompt)?; user_prompt = user_prompt.trim().to_string(); if user_prompt.to_lowercase() == "quit" { break; } // ... } ``` ### Setting the prompt Now, we'll create a prompt string that includes the user's question and the CSV data. This prompt will be used by the `llm_chain` crate to generate a response. > 💡 **TIP**: When defining prompts, be clear and concise about the task you want the language model to perform. Provide any necessary context or input data (like the CSV example) and be specific about the desired output (eg, a summary, analysis, code, or text generation). ```rust let prompt_string = format!( "You are a data analyst tasked with analyzing a CSV file containing information about individuals, including their name, age, occupation, city, favorite sport, and annual income. Your goal is to provide clear and concise answers to the given questions based on the data provided. Question: {}\n\nCSV Data:\n{}", user_prompt, csv_data ); ``` ### Creating a Step instance We create a Step instance from the `llm_chain` crate, passing in the prompt string we created earlier. Steps are individual LLM invocations in a chain. They are a combination of a prompt and a configuration and we use them to set the per-invocation setting for a prompt. This comes in very handy when we want to change the settings for a specific prompt in a chain. ```rust let step = Step::for_prompt_template(prompt!("{}", &prompt_string)); ``` ### Connecting everything together We run the analysis by calling the `run` method on the `Step` instance, passing in the parameters and the executor we created earlier. ```rust let res = step.run(&parameters!(), &exec).await?; ``` ### Outputting the result This one is as simple as it gets; we invoke the `println!()` macro print the result of the analysis to the console. ```rust println!("{}", res.to_immediate().await?.as_content()); ``` That's the end of our loop and now we need to wrap it all up by adding `Ok(())` at the end of the main function to indicate that the function executed successfully without any errors. ### Running our App To run the app, navigate to the project directory and execute the following command: `cargo run` You should see the prompt `Enter your prompt (or 'quit'` to exit):. Enter your question related to the data in the data2.csv file, and the app will provide a concise answer based on the analysis. Here are some CSV-specific questions you can ask your helper: 1. "Who has the highest annual income, and what is their occupation?" 2. "What is the most popular extreme sport among the individuals in the data?" 3. "Which city has the most individuals represented in the data?" 4. "What is the average age of the individuals whose favorite sport is rock climbing?" 5. "Which occupation has the highest average annual income?" ### Challenge Sharing a CLI app with your friends or team mates might not be too straight-forward. As a challenge, try building an API around your helper with endpoints that'll allow users to interact with it. Next, build a simple frontend with a small prompt box that users can use to ask your helper various questions, it should communicate with your API. And finally, hook it all up together and try deploying it with Shuttle! P.S. Tweet `#shuttleai` when you are done and we'll check out and share your creations! ## Summary In this tutorial, we built a command-line application in Rust that can analyze data from a CSV file. The app uses the `csv` and `llm_chain` crates to read the CSV data and generate a response based on the user's question. If you are up for another challenge; try making the data source dynamic, allowing users to upload their own `csv`, or even better, other file formats such as `pdf` or `json`! Read more: - [Creating a RAG-assisted web service with Axum](https://www.shuttle.rs/blog/2024/02/28/rag-llm-rust) - [More than you've ever wanted to know about error handling](https://www.shuttle.rs/blog/2022/06/30/error-handling)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully