Anthropic Claude Tutorial: Building a Simple and Safe Collaborative Writing App

Thursday, June 15, 2023 by septian_adi_nugraha408
Anthropic Claude Tutorial: Building a Simple and Safe Collaborative Writing App

Introduction

Anthropic's Claude and AI Safety

In the burgeoning era where AI not only gains more power but also becomes more prevalent, prioritizing safety and ethical considerations in building and operating our AI-powered applications is of utmost importance.

Anthropic, a leading research organization in the field of AI, has developed a model named Claude, focusing specifically on safety. Recognizing the significant influence AI will have in the coming decade, they argue for the necessity of understanding how to render such systems safe and aligned with human values.

Introducing React and TailwindCSS

This tutorial utilizes React, a renowned Javascript/Typescript library for constructing user interfaces. Developed and maintained by Facebook, React has emerged as a favored solution for developers worldwide, owed to its efficiency, flexibility, and powerful features. It enables the creation of interactive web applications that respond efficiently to data changes, regardless of the size or complexity of the applications.

Whether you're an old hand at React or new to it, this tutorial will introduce you to the core principles that make React so compelling, such as components, state, props, and its lifecycle. Newcomers will discover how React simplifies the process of developing user interfaces, courtesy of the declarative paradigm it espouses.

Additionally, we will employ Tailwind CSS in this tutorial, a highly customizable, low-level CSS framework. It saves us from the nuisance of "fighting the framework" or working around the opinionated styles that many CSS frameworks impose. Rather than offering pre-designed components, Tailwind provides low-level utility classes that allow you to create entirely custom designs. This "utility-first" approach enhances the reusability and maintainability of our CSS.

Utilizing Flask for Backend

We will be employing Flask as our backend for this application. Flask is a Python-based web framework celebrated for its lightweight structure and robust capabilities. Its principal charm lies in its simplicity, flexibility, and the fine-grained control it offers. Unlike other frameworks, Flask doesn't impose many decisions, providing us with the freedom to select the tools and libraries best suited to our needs. As a "micro" framework, it caters to a wide spectrum of applications, from the creation of basic web pages to the development of intricate RESTful APIs.

Prerequisites

  • Basic knowledge of Typescript programming, bonus point for experience in React
  • Basic knowledge of Python programming, bonus point for experience in frameworks such as Flask
  • Access to Anthropic's Claude API
  • Basic knowledge of UI development using markup such as HTML and CSS

Outline

  1. Initializing the Project
  2. Setting Up the Required Libraries
  3. Writing the Project Files
  4. Testing the Help Desk App
  5. Setting Up Chroma Database
  6. Testing the Help Desk App

Discussion

Initializing the Project

We're now prepared to dive into the code! First, we need to ensure our development environment is primed. This tutorial will start from the assumption of a fresh installation, so we'll begin by installing Node.js (a JavaScript runtime that allows us to run JavaScript on our server) and npm (a package manager bundled with Node.js).

Front-End

Installing Node.js and NPM

  1. Download the Node.js installer for your OS from the official site.
  2. Follow the installation prompts to install Node.js and npm. The LTS (Long Term Support) version is recommended for most users.
  3. Once installed, verify the installation by checking the versions of Node.js and npm from your terminal:
# Verify Node.js installation
node -v
# Verify npm installation
npm -v

Installing Create React App

Create React App (CRA) is a command-line utility that assists us in creating a new React.js application. We'll install it globally via npm:

npm install -g create-react-app

Creating a New React Project with Typescript

We'll use CRA with the Typescript template to create a new project named write-with-claude.

npx create-react-app write-with-claude --template typescript

This command creates a new directory called write-with-claude in our current directory, housing a new React application with Typescript support.

Installing TailwindCSS

The steps followed in this tutorial are based on the official Tailwind CSS documentation. Refer to these docs for more up-to-date instructions.

We'll start by installing TailwindCSS and initializing the library in our project:

# Install TailwindCSS library
npm install -D tailwindcss

# Initialize Tailwind CSS, generating `tailwind.config.js` file
npx tailwindcss init

Next, we configure our template paths by adding them to the tailwind.config.js file:

/** @type {import('tailwindcss').Config} */
module.exports = {
--    content: [],
++  content: [
++    "./src/**/*.{js,jsx,ts,tsx}",
++  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Lastly, we'll add the @tailwind directives for each of Tailwind's layers to our ./src/index.css file:

@tailwind base;
@tailwind components;
@tailwind utilities;

And voila! Tailwind CSS is now integrated into our project.

Installing Various Libraries

Before we proceed to the coding section, let's finalize our preparations by installing the necessary libraries such as fontawesome and react-markdown for the icons and rendering purposes.

# Install Fontawesome icon
npm i --save @fortawesome/fontawesome-svg-core
npm install --save @fortawesome/free-solid-svg-icons
npm install --save @fortawesome/react-fontawesome

# Install React Markdown library
npm install react-markdown

Back-End

Setting Up Flask Framework

At this point, let's return to our main project directory. We'll create a new subdirectory for the backend, which we'll name write-with-claude-backend.

cd ..
mkdir write-with-claude-backend
cd write-with-claude-backend

Next, we'll set up a virtual environment and activate it:

# Create the virtual environment named `env`
python3 -m venv env
# Activate the virtual environment
source env/bin/activate

Upon activation, the environment's name should appear next to our directory in the terminal.

The virtual environment name (env) shows up next to the directory name

Now we're ready to install the necessary libraries: flask for the web framework, anthropic for Anthropic's Python SDK, and python-dotenv to handle environment variables.

# Install Flask framework
pip install flask
# Install Anthropic's Python SDK
pip install anthropic
# Install python-dotenv library to handle environment variables
pip install python-dotenv

Great! With these installations complete, our project is now set for further development.

Writing the Project Files

Let's delve back into our coding section. As we will write for both our front-end and back-end codes, let's begin with the front-end first.

Front-End

Let's change directory to our front-end project again. If the setup process from earlier using create-react-app is done successfully, we should have App.tsx file ready to edit. This is the only file we will edit to build our user interface, thanks to TailwindCSS which already took care of our styling, all in one place.

App.tsx ⚛️

Let's begin with the most important statements in a React component, the import statements themselves! To be fair, if you use an IDE with autocomplete and auto-import features such as Visual Studio Code or Webstorm by Jetbrains, this import statement will be automatically solved and generated. Still, it's important to make sure that we covered all grounds! We also load an animated GIF image which we will refer to as loadingIndicator.

// Importing the necessary libraries and components.
import React, { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlusCircle, faStarOfLife } from '@fortawesome/free-solid-svg-icons';
import loadingIndicator from './loading.gif'
import  ReactMarkdown  from 'react-markdown';

Let's start our App component! While there are many ways to write a React component, I chose functional component as I personally am a fan of React Hooks and functional component really helps in writing clean and declarative UI components. Hooks are functions that allow us to use state and other React features without writing a class.

// The main component.
function App() {

  // Define the Section interface, which represents a title and content pair.
  interface Section {
    title: string,
    content: string
  }

In the beginning of my component, I defined an interface called Section to represent the sections of our UI, which contains title and content. A little note here, an 'interface' in Typescript works like a 'struct in other languages such as C++ or Rust. Interface provides a way to define the shape of an object, making the code more predictable and easier to understand.

Now, it is high time that we introduce Hooks to our component! Let's do it now.

// Initialise the state for various variables.
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState('');
  const [title, setTitle] = useState("");
  const [sections, setSections] = useState<Section[]>([{ title: '', content: '' }]);
  const [completion, setCompletion] = useState("")

We're initializing several state variables using React's useState hook:

  • isLoading for the loading state
  • error for error messages
  • title for the title of the document
  • sections for the sections of the document
  • completion for the response from the API
// Function to handle changes in section fields (title/content).
const handleSectionChange = (field: 'title' | 'content', value: string, index: number) => {
    const newSections = [...sections];
    newSections[index][field] = value;
    setSections(newSections);
  };

handleSectionChange is a function that is called whenever the title or content of a section is changed. It updates the state of the sections variable.

// Function to add a new section.
const handleAddSection = () => {
    const newSections = [...sections];
    newSections.push({ title: '', content: '' })
    setSections(newSections)
  }

handleAddSection is a function that is called to add a new section to your document. It pushes a new section with empty title and content into the sections array and updates the state.

const handleSubmit = () => {
    if (sections.some(section => section.title === '' || section.content === '')) {
      setError('Please make sure all sections are filled.');
    } else {
      setIsLoading(true);
      setError('');
      const yourData = {
        title: title,
        sections: sections,
      }
      fetch('/completion', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(yourData),
      })
        .then((response) => response.json())
        .then((data) => {
          console.log('Success:', data);
          setCompletion(data.completion)
        })
        .catch((error) => {
          console.error('Error:', error);
        });
    }
  }

handleSubmit is a function that sends a POST request to the /completion endpoint with the title and sections as the body. It also sets isLoading to true to show the loading gif animation, and sets it to false when the response is received. Note that fetch() is an asynchronous operation that returns a Promise, which is why we use .then() to handle the result.

return (
    <div className="bg-gray-100 min-h-screen flex items-center justify-center">
      <div className="bg-white p-8 rounded-lg shadow-md w-3/4">
        <h1 className='font-bold text-2xl mb-6 text-center'>Write with Claude</h1>
        <div className='grid grid-cols-2 gap-4'>
          <div className='space-y-4'>
            <div className='space-y-1'>
              <label htmlFor="title" className='text-sm font-semibold'>Title</label>
              <input id="title" type="text" className='border p-2 w-full' value={title} onChange={e => setTitle(e.target.value)} />
            </div>
            {sections.map((v: Section, i: number) => (
              <div key={`section_${i}`} className='space-y-2'>
                <div className='flex gap-2'>
                  <label htmlFor={`section_${i}`} className='text-sm font-semibold'>Section: </label>
                  <input
                    id={`title_${i}`}
                    type="text"
                    className='border p-2 w-full'
                    value={v.title}
                    onChange={(event) => handleSectionChange('title', event.target.value, i)}
                  />
                </div>
                <textarea
                  id={`section_${i}`}
                  onChange={(event) => handleSectionChange('content', event.target.value, i)}
                  className='border p-2 w-full'
                  value={v.content}
                  cols={30}
                  rows={5}
                />
              </div>
            ))}
            <div className='flex w-full gap-2'>
              <div onClick={handleAddSection} className='flex hover:cursor-pointer w-8/12 bg-amber-700 items-center gap-2 rounded-sm px-2 py-1 text-sm text-slate-200'>
                <FontAwesomeIcon icon={faPlusCircle} />
                <p>Add new section.</p>
              </div>
              <div onClick={handleSubmit} className='bg-emerald-600 hover:cursor-pointer flex items-center gap-2 w-4/12 text-slate-200 text-sm rounded-sm py-3 px-2'>
                <FontAwesomeIcon icon={faStarOfLife} />
                <p>Brainstorm</p>
              </div>
            </div>
          </div>
          <div className='p-4 border text-sm border-gray-300 rounded-lg flex flex-col justify-center text-gray-600'>
            <div className='absolute flex'>
              {
                isLoading &&
            <img src={loadingIndicator} alt=" " className='z-10 w-28 self-center' />
              }
            </div>
            {!isLoading && completion ?
              <ReactMarkdown>{completion}</ReactMarkdown>
              :
              <ReactMarkdown>Get started with collaborative writing with Claude!</ReactMarkdown>
            }
          </div>
        </div>
      </div>
    </div>
  );

Finally, this is our return statement, where we define the HTML-like JSX to render the component. The main elements include the form for the user to iput the title and sections, a button to add new sections, a button to submit the form, and a space to display the completion from the API. While isLoading is true, a loading gif will be displayed. After the API responds, isLoading will be set to false and the response from the API (stored in completion) will be displayed. If there's no response yet, a default message will be displayed.

// Exporting the component.
export default App;

Finally, we export the App function as the default export of the module so that it can be imported and used elsewhere in your project.

Phew, now, if everything goes well, this is how our App.tsx will look like:

import React, { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlusCircle, faStarOfLife } from '@fortawesome/free-solid-svg-icons';
import loadingIndicator from './loading.gif'
import  ReactMarkdown  from 'react-markdown';


function App() {

  interface Section {
    title: string,
    content: string
  }


  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState('');
  const [title, setTitle] = useState("");
  const [sections, setSections] = useState<Section[]>([{ title: '', content: '' }]);
  const [completion, setCompletion] = useState("")

  const handleSectionChange = (field: 'title' | 'content', value: string, index: number) => {
    const newSections = [...sections];
    newSections[index][field] = value;
    setSections(newSections);
  };

  const handleAddSection = () => {
    const newSections = [...sections];
    newSections.push({ title: '', content: '' })
    setSections(newSections)
  }

  const handleSubmit = () => {
    if (sections.some(section => section.title === '' || section.content === '')) {
      setError('Please make sure all sections are filled.');
    } else {
      setIsLoading(true);
      setError('');
      const yourData = {
        title: title,
        sections: sections,
      }
      fetch('/completion', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(yourData),
      })
        .then((response) => response.json())
        .then((data) => {
          console.log('Success:', data);
          setCompletion(data.completion)
        })
        .catch((error) => {
          console.error('Error:', error);
        });
    }
  }

  return (
    <div className="bg-gray-100 min-h-screen flex items-center justify-center">
      <div className="bg-white p-8 rounded-lg shadow-md w-3/4">
        <h1 className='font-bold text-2xl mb-6 text-center'>Write with Claude</h1>
        <div className='grid grid-cols-2 gap-4'>
          <div className='space-y-4'>
            <div className='space-y-1'>
              <label htmlFor="title" className='text-sm font-semibold'>Title</label>
              <input id="title" type="text" className='border p-2 w-full' value={title} onChange={e => setTitle(e.target.value)} />
            </div>
            {sections.map((v: Section, i: number) => (
              <div key={`section_${i}`} className='space-y-2'>
                <div className='flex gap-2'>
                  <label htmlFor={`section_${i}`} className='text-sm font-semibold'>Section: </label>
                  <input
                    id={`title_${i}`}
                    type="text"
                    className='border p-2 w-full'
                    value={v.title}
                    onChange={(event) => handleSectionChange('title', event.target.value, i)}
                  />
                </div>
                <textarea
                  id={`section_${i}`}
                  onChange={(event) => handleSectionChange('content', event.target.value, i)}
                  className='border p-2 w-full'
                  value={v.content}
                  cols={30}
                  rows={5}
                />
              </div>
            ))}
            <div className='flex w-full gap-2'>
              <div onClick={handleAddSection} className='flex hover:cursor-pointer w-8/12 bg-amber-700 items-center gap-2 rounded-sm px-2 py-1 text-sm text-slate-200'>
                <FontAwesomeIcon icon={faPlusCircle} />
                <p>Add new section.</p>
              </div>
              <div onClick={handleSubmit} className='bg-emerald-600 hover:cursor-pointer flex items-center gap-2 w-4/12 text-slate-200 text-sm rounded-sm py-3 px-2'>
                <FontAwesomeIcon icon={faStarOfLife} />
                <p>Brainstorm</p>
              </div>
            </div>
          </div>
          <div className='p-4 border text-sm border-gray-300 rounded-lg flex flex-col justify-center text-gray-600'>
            <div className='absolute flex'>
              {
                isLoading &&
            <img src={loadingIndicator} alt=" " className='z-10 w-28 self-center' />
              }
            </div>
            {!isLoading && completion ?
              <ReactMarkdown>{completion}</ReactMarkdown>
              :
              <ReactMarkdown>Get started with collaborative writing with Claude!</ReactMarkdown>
            }
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;

With our UI component done, let's tweak other configuration files for our front-end.

package.json 📦

Our package.json file serves as a manifest for our project, housing metadata about the libraries we use, as well as other configurations such as scripts and proxies. One crucial field we'll add here is the proxy field, which we'll set to http://localhost:5000. This field allows our front-end application to redirect any unknown requests (API calls, for example) to the specified proxy. This is particularly handy as our back-end server will be running on localhost:5000.

{
  "name": "write-with-claude",
  "version": "0.1.0",
  "private": true,
++  "proxy": "http://localhost:5000",
  "dependencies": {
    "@anthropic-ai/sdk": "^0.4.4",

index.html

Lastly, while it's not required, updating the title of your app can serve as a charming final touch before we run it. This change updates the title displayed on the browser tab. Let's adjust the title to "Write with Claude".

<head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
--    <title>Create React App</title>
++    <title>Write with Claude</title>
  </head>

Note: In the diff blocks above, ++ denotes additions while -- represents deletions.

Back-End

Let's change directory to our backend project, and start creating a Python file called app.py, which is our backend file run by Flask later on, an .env file to store our Anthropic API key, and finally, generate our requirements.txt.

app.py

Let's begin by importing all the necessary libraries.

import os
from flask import Flask, request
import anthropic

We are importing os which is a module for interacting with the operating system, which we will use to get our environment variables. Flask is a web framework, request is a Flask object that contains information about the request being made to the server, and anthropic is a library for interacting with the Anthropic AI model.

app = Flask(__name__)

We're creating a new Flask web server. __name__ ia a special variable in Python that gets as value the string "__main__" when we're runnning the script directly, and the name of the module when it's imported.

@app.route("/completion", methods=['POST'])
def completion():

Then, we'll start defining our route! For the purpose of this tutorial, we will only make one endpoint which will be called by the frontend to generate response. We call the Anthropic API via SDK because as of now, there seems to be a CORS issue when calling it directly from the frontend. In this case, we're defining a route /completion that listens for a HTTP POST requests.

data = request.get_json()  # Get the JSON data from request
title = data.get('title')
sections = data.get('sections')

Inside the route handler, we're getting the JSON data sent with the request, and extracting the title and sections from it. get_json() is a method of the request object that parses the incoming JSON request data and returns it as a Python dictionary.

prompt = (f"{anthropic.HUMAN_PROMPT} I need your help to collaboratively write an article. The article has a title and several sections. "
          f"I will provide you with the title and the contents of the sections, and I want you to help me improve them. "
          f"Your improvements should focus on enhancing the readability, correcting any grammatical errors, and improving the writing style while keeping the original meaning and intent of the text intact. "
          f"Feel free to expand and develop the writing while maintaining coherence of the overall writing."
          f"Please also ensure that the content generated is safe and free from harmful contents such as violence, sex, swear words, hate speech, and warmongering. "
          f"Finally, please use markdown to write your revision, so I can render it on my user interface."
          f"{anthropic.AI_PROMPT} Understood, I will assist you in improving the provided text while ensuring the content safety. "
          f"{anthropic.HUMAN_PROMPT} Great, here is the title and the sections for you to revise. The title is {title}")

Next, we will define our base prompt in a variable aptly named prompt which contains the instruction for the AI, followed by the article's title and sections. This prompt employs method to ensure AI safety, which is "instruction following" and "AI transparency". We explicitly mentioned that we want the AI to improve the readability, grammar, and style of the text, while avoiding harmful content. AI transparency means that we are asking the AI to state its understanding of the task, which provides some level of transparency to its thought process.

for i, section in enumerate(sections):
    prompt += f"; Next section - title: {section['title']}; section content: {section['content']}"

After that, we begin appending our base prompt with the input from the frontend, namely the article title, and the sections of the article, which contains the section title and content.

c = anthropic.Client(os.getenv("CLAUDE_KEY"))
resp = c.completion(
    prompt=f"{prompt} {anthropic.AI_PROMPT}",
    stop_sequences=[anthropic.HUMAN_PROMPT],
    model="claude-v1.3-100k",
    max_tokens_to_sample=1500,
)

return resp

Finally, we're creating a new instance of the Anthropic API client and then requesting a text completion form the Claude model. We're setting the prompt, specifying the model to use, and indicating the maximum number of token to sample. After that, we directly return the response from the Anthropic API to our client/frontend.

If everything's set up correctly, this is how our app.py would look like:

import os
from flask import Flask, request
import anthropic

app = Flask(__name__)

@app.route("/completion", methods=['POST'])
def completion():
    data = request.get_json()  # Get the JSON data from request
    title = data.get('title')
    sections = data.get('sections')

    prompt = (f"{anthropic.HUMAN_PROMPT} I need your help to collaboratively write an article. The article has a title and several sections. "
          f"I will provide you with the title and the contents of the sections, and I want you to help me improve them. "
          f"Your improvements should focus on enhancing the readability, correcting any grammatical errors, and improving the writing style while keeping the original meaning and intent of the text intact. "
          f"Feel free to expand and develop the writing while maintaining coherence of the overall writing."
          f"Please also ensure that the content generated is safe and free from harmful contents such as violence, sex, swear words, hate speech, and warmongering. "
          f"Finally, please use markdown to write your revision, so I can render it on my user interface."
          f"{anthropic.AI_PROMPT} Understood, I will assist you in improving the provided text while ensuring the content safety. "
          f"{anthropic.HUMAN_PROMPT} Great, here is the title and the sections for you to revise. The title is {title}")

    for i, section in enumerate(sections):
        prompt += f"; Next section - title: {section['title']}; section content: {section['content']}"

    c = anthropic.Client(os.getenv("CLAUDE_KEY"))
    resp = c.completion(
        prompt=f"{prompt} {anthropic.AI_PROMPT}",
        stop_sequences=[anthropic.HUMAN_PROMPT],
        model="claude-v1.3-100k",
        max_tokens_to_sample=1500,
    )
    return resp

.env 🌏

Our .env file for the backend project only contains one variable, which is CLAUDE_KEY, which contains API key to access Anthropic API.

CLAUDE_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxx

requirements.txt 📄

The last step of our coding involves recording the list of libraries we use into a file called requirements.txt. Thankfully, this process can be done by the pip command. Make sure we have our virtual environment activated, and then run this command:

# to record the dependencies
pip freeze > requirements.txt
# to install the listed dependencies
pip install -r requirements.txt

That's it! Now other users will be able get started quickly with running our project by installing the libraries listed in our file, using a simple command too!

Testing the App

Now, let's move on to the exciting part, testing our application! We'll start by running our Flask backend with the following command:

flask run

Upon executing this command, you should see an output similar to the one in the image below, indicating the Flask backend is up and running.

The Flask backend is running

Before proceeding to test the front-end, it's a good idea to test our API endpoint using a tool like Insomnia. We'll send a JSON payload formatted as shown in the image below. The backend will parse this data before sending it to the Anthropic API.

The response of our API endpoint, returning completion from Anthropic API

With the backend tested, we're now ready to test the front-end. Ensure the backend is still running. Open a new terminal, navigate to the front-end project directory, and run the following commands:

# change directory to our front-end
cd ~/[project_directory]/write-with-claude
# run npm start to start our react app
npm start

You should see an output similar to the image below, indicating that the React app has been successfully built and is ready to go. The app should open in your default browser automatically.

The create react app output, indicating the app is built successfully and ready to go

You'll see our user interface, as shown in the image below. Thanks to Tailwind CSS, we've been able to write a clean and concise front-end using utility-based CSS classes. Let's begin by filling out the form.

The view of our user interface, with the app title, user inputs for article title and its sections, buttons to add more sections and to 'brainstorm', or send the inputs as prompts, finally, there's a container to display the response from the Anthropic API

Let's start our collaborative writing process using the "Write with Claude" app by filling out the title field and adding sections for our article. Normally, an article should start with an introduction, so let's add "Introduction" as our first section.

We start filling out the user input, starting with title, and the introduction to the article, we also add more section, 'discussion', to further explore our topic

Try clicking the "Add new section" button. You'll see a new empty field for the next section. While we're here, let's fill in the "Discussion" section for the article.

When you're ready to have the Anthropic API complete your inputs, click the "Brainstorm" button. You'll see a loading indicator, signifying that your inputs are being sent to the backend and formatted as prompts for the Anthropic API. After a few seconds, the AI's completion should appear on the right.

the loading indicator should show up when we click 'Brainstorm' button

Let's examine the AI's response! As you can see in the image below, the app is functioning well. The AI both revised our writing and expanded on the theme based on the title and sections we provided.

the response from Anthropic Claude API is displayed. The AI successfully provided the revision as well as expansion of our original idea.

Finally, let's address the issue of AI safety. As we mentioned in our backend code, we've set up safety protocols to prevent the AI from engaging with harmful topics. To test this, let's add a "Conclusion" section, suggesting the harmful idea of "Let's forget about creativity and wage more wars instead!". How will the AI respond?

We add a conclusion, suggesting to abandon the discussion on creativity and choose to wage war instead

After adding our provocative "Conclusion" section, click the "Brainstorm" button again.

the response from Anthropic Claude API is displayed again. This time, the AI refused to elaborate on the idea of warmongering, and instead suggested that we consider how to develop AI's creative abilities

The AI, as expected, quotes our harmful idea about warmongering and immediately rejects it. The response gracefully pivots the discussion back to the main topic at hand, which demonstrates the safety measures in action.

By including this test, we highlight the importance of AI safety and showcase how our app implements measures to ensure ethical AI usage. If your AI application doesn't respect these safety guidelines, it could potentially spread or endorse harmful ideas, which is why we've prioritized this aspect in "Write with Claude."

Wrapping Up

Voilà! This comprehensive anthropic tutorial navigated through both backend and frontend aspects of app development, illuminating AI-powered collaborative writing. Emphasizing crucial AI safety elements, we spotlighted Anthropic as a trailblazer in this domain. By adhering to stringent safety protocols, the anthropic app is protected against misuse, such as harmful content generation. Furthermore, Anthropic's Claude model boasts remarkable, human-like linguistic prowess, skillfully transitioning from negating harmful ideas to resuming the core topic.

Sincere thanks for joining this journey! May you find the coding steps as captivating as the experience of crafting this anthropic application! Explore the completed project on Github, featuring both the backend and frontend. Until the next adventure!