Skip to content

How System Prompts Change AI Behavior: Same Model, Different Results

Problem

I noticed something strange when testing the same AI model across different tools. Claude Code behaved completely differently from the Claude API. Same model name, same version, same weights. But the outputs were night and day different.

# Claude Code output:
Let me think through this step by step...
1. First, I need to understand the context...
2. Then I should verify my assumptions...
3. Now let me check if this matches the requirements...
# Claude API direct call output:
Here's the answer: [solution]

I was confused. How can the “same model” produce such different behaviors?

What I Tested

I ran a simple experiment using Claude 3.5 Sonnet in two configurations:

  1. Direct API call - minimal system prompt, just the user message
  2. Claude Code - the development tool built on the same model

Here’s my test prompt:

Write a function to validate email addresses in Python.

Direct API Response

Here's a Python function to validate email addresses:
import re
def validate_email(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))

The response was fast, direct, and correct.

Claude Code Response

I'll help you create an email validation function. Let me think about the requirements first:
1. Email format: local-part@domain
2. Local part can contain: letters, numbers, dots, underscores, plus, hyphens
3. Domain must have at least one dot
4. TLD should be at least 2 characters
Here's my implementation:
```python title="email_validator.py"
import re
from typing import Tuple
def validate_email(email: str) -> Tuple[bool, str]:
"""
Validate an email address format.
Args:
email: The email address to validate
Returns:
Tuple of (is_valid, error_message)
"""
if not email:
return False, "Email cannot be empty"
if len(email) > 254:
return False, "Email exceeds maximum length"
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
return False, "Invalid email format"
return True, ""

Wait, let me also add some test cases to verify this works correctly…

[proceeds to write tests and verify edge cases]

The Claude Code response was slower, more thorough, and included reasoning.
## The Key Difference
The difference wasn't the model. It was the system prompt.
```text
# Simplified system prompt comparison
Direct API:
"You are a helpful assistant."
Claude Code:
"You are a thorough developer assistant.
Always verify your work before claiming completion.
Explain your reasoning process.
Test edge cases when writing code."

The same model, the same weights, the same context window. Different instructions about how to think and respond.

How System Prompts Work

System prompts sit at the top of the prompt hierarchy:

┌─────────────────────────────────────┐
│ System Prompt │ ← Highest priority
│ "You are a thorough developer..." │ Defines behavior mode
├─────────────────────────────────────┤
│ Context Window │ ← Current conversation
│ Previous messages, files, etc. │ Provides information
├─────────────────────────────────────┤
│ User Prompt │ ← Immediate request
│ "Write a function to..." │ Specific task
└─────────────────────────────────────┘

The system prompt sets the “mode of operation” before any user input arrives. It’s not about what the model can do - it’s about how the model should behave.

Three Core Mechanics

1. Priority Framing

# Speed-focused system prompt:
"Provide direct answers immediately. Minimize explanation."
# Accuracy-focused system prompt:
"Think through each step carefully. Verify before responding."

The same model will optimize for different metrics based on this framing.

2. Output Format Constraints

# Answer-first format:
"Lead with the answer, then explain if requested."
# Reasoning-first format:
"Explain your thought process before giving the answer."

This changes not just what is said, but the order in which the model processes information.

3. Role Definition

# Executor role:
"You are a code generator. Output working code."
# Collaborator role:
"You are a development partner. Discuss trade-offs and ask clarifying questions."

The executor rushes to completion. The collaborator engages in dialogue.

Observable Behavior Differences

I ran another test with a debugging task:

Debug this code:
def calculate_average(numbers):
return sum(numbers) / len(numbers)

Direct API (Minimal System Prompt)

The code works correctly for non-empty lists.
For empty lists, add a check:
def calculate_average(numbers):
if not numbers:
return 0
return sum(numbers) / len(numbers)

Fast, correct, minimal explanation.

Claude Code (Full System Prompt)

Let me analyze this code for potential issues:
1. The function divides sum by length
2. If numbers is empty, len(numbers) = 0
3. Division by zero will raise ZeroDivisionError
Before fixing, let me consider:
- What should the average of an empty list be?
- Return 0? Raise an exception? Return None?
- This is a design decision, not just a bug fix
Here's my recommended approach:
```python title="average.py"
from typing import List, Optional
def calculate_average(numbers: List[float]) -> Optional[float]:
"""
Calculate the average of a list of numbers.
Returns:
The average, or None if the list is empty.
"""
if not numbers:
return None
return sum(numbers) / len(numbers)

I chose to return None for empty lists because:

  • Returning 0 could hide bugs in calling code
  • None explicitly signals “no valid average”
  • Callers must handle this case explicitly

Should I show how to use this in calling code?

Same model. Different behaviors.
## The Hidden Cost
System prompts are easy to change. But the side effects aren't always obvious.
I tested a system prompt optimization that improved speed by 40%. But it also:
- Reduced code comment quality by 60%
- Increased rate of edge case bugs by 25%
- Lowered user trust scores (responses felt "rushed")
The trade-off wasn't visible in the speed metric alone.
```text
# What I measured initially:
Response time: ↓ 40% ✓
# What I discovered later:
Code comments: ↓ 60% ✗
Edge case bugs: ↑ 25% ✗
User trust: ↓ 15% ✗

System prompt changes require holistic testing, not just single-metric optimization.

Why This Matters

Understanding system prompts changes how I think about AI tools:

  1. Tool comparison is misleading - When comparing Claude API vs Claude Code, I’m not comparing models. I’m comparing system prompts.

  2. Behavior is configurable - I don’t need a different model for different behaviors. I need different system prompts.

  3. Debugging requires context - When AI behaves unexpectedly, check the system prompt first.

  4. Optimization has trade-offs - Speed gains often come with quality losses.

How to Design System Prompts

Based on my experiments, here’s what works:

# Good system prompt structure:
1. Role definition: Who is the AI?
2. Priority ranking: What matters most?
3. Output format: How should responses look?
4. Behavior guardrails: What should/shouldn't happen?

Example for a code review assistant:

You are a code review assistant focused on security and performance.
Priority order:
1. Security vulnerabilities (critical)
2. Performance issues (high)
3. Code style (medium)
4. Minor suggestions (low)
Output format:
- Start with severity rating
- Explain the issue
- Provide specific fix
- Show corrected code
Guardrails:
- Never approve code with security vulnerabilities
- Always suggest alternatives when rejecting an approach
- Explain reasoning, don't just state rules

The specificity matters more than the length.

Summary

In this post, I demonstrated how system prompts control AI behavior independent of model weights. The same model produces fundamentally different outputs based on system prompt instructions about priority, format, and role.

The key takeaway: when you see different AI tool behaviors, don’t assume different models. Check the system prompts first. The behavior difference you observe is likely intentional design, not model limitation.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments