System Prompts

Suppose we are building a math tutor chatbot. A student arrives, anxious and hopeful, and types: “How do I solve 5x + 2 = 3 for x?”

And Claude, catastrophically helpful as always, hands over the complete step-by-step solution. Subtract 2 from both sides, divide by 5, here is your answer, have a lovely day.

The student copies it down, learns nothing, and fails the exam.

What a Tutor Actually Does #

A real tutor gives hints before solutions. A real tutor walks the student through the problem step by step, patiently, and demonstrates with similar problems rather than solving the one on the worksheet.

And there are things a real tutor never does: like blurt out the answer, or tell the student to go use a calculator.

The problem is not what Claude knows. The problem is how Claude behaves. System prompts let us calibrate Claude’s behaviour without changing the student’s question.

The System Prompt #

System prompts guide how to respond. Claude gets these instructions before the conversation begins. You write it as a plain string and pass it to the create call.

const system = `
You are a patient math tutor.
Do not directly answer a student's questions.
Guide them to a solution step by step.
`

Claude will try to respond the way someone in the specified role would respond. It also helps keep Claude on task. So a tutor who stays a tutor, message after message.

const message = await client.messages.create({
  model,
  max_tokens: 1000,
  messages,
  system,
});

The first line assigns the role: “You are a patient math tutor”, and the lines after it give specific behavioural instructions. Note what the system prompt does not contain: mathematics. It controls how Claude responds, never what it responds about.

With the tutor prompt, Claude asks: “What do you think would be a good first step to isolate x? Consider what operation we might need to perform on both sides to start moving terms around.”

Same model. Same question.

A More Flexible Chat Function #

Hard-coding the system prompt into our chat function would be a tragedy for reusability.

Instead, we accept it as an optional parameter.

async function chat(
  messages: Anthropic.MessageParam[],
  system?: string,
): Promise<string> {
  const message = await client.messages.create({
    model,
    max_tokens: 1000,
    messages,
    ...(system ? { system } : {}),
  });

  const block = message.content[0];
  return block?.type === "text" ? block.text : "";
}

The API does not accept an empty or null system prompt. So you must include the system parameter only when you actually have one. The conditional spread ...(system ? { system } : {}) adds the key when a prompt is provided and adds nothing at all when it is not.

Now both worlds are available on demand.

// Without a system prompt
const answer = await chat(messages);

// With a system prompt
const system = `
You are a patient math tutor.
Do not directly answer a student's questions.
Guide them to a solution step by step.
`;

const tutorAnswer = await chat(messages, system);

That is the way.

Repo #

Code exercises set up here

 
0
Kudos
 
0
Kudos

Now read this

Switching Between Staging and Production Firebase Deploys In A Create-React-App

When building web applications on firebase, it is always a good idea to push the final application to the brand new firebase project. Go into your firebase console and setup a new firebase project. Explicitly name this project... Continue →