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

An Introduction to Software Testing

Qunit is a javascript framework that you can use to test software as you build it. Learning how to use qunit so that you can test software as you build it will save you time in the long run. Testing will ensure that you only write the... Continue →