LLM Migration: GPT to Claude to Open Source
Imagine: your service running on GPT-4 starts delivering irrelevant answers after switching to Claude. Or you want to move to open source models to cut costs but fear losing quality. We've faced this dozens of times. Our experience shows: a properly planned migration not only preserves but can improve quality by choosing a better model for your task.
Consider a specific case: migrating from GPT-4 to Claude 3 Sonnet for a support chatbot. We adapted 150 prompts, rewrote tool call logic, and ran A/B testing. Result: 60% cost reduction (from $10,000/month to $4,000/month) with a 10% CSAT increase. This is not an isolated example: we have completed over 20 successful migrations in the past 5 years. Typical annual savings for our clients range from $5,000 to $30,000. One client cut their API costs from $12,000 to $3,500 after moving to an open-source model.
Problems Encountered During Migration
- Different prompt formats. OpenAI uses a messages array with a system role; Claude uses a separate system parameter with XML tags. Directly transferring the system prompt causes loss of some instructions.
- Different tool call structures. OpenAI uses functions with parameter descriptions; Claude uses tool_use with input_schema. Conversion requires type mapping and strict format adherence.
- Different tokenization models and context windows. GPT-4 has an 8k/32k window; Claude 3 has 200k; open source models (LLaMA 3, Mistral) have 8k-32k. This affects strategies for splitting long texts.
- Quality loss. Even with identical prompts, models behave differently. Without testing, accuracy can drop by 20-30%.
How We Solve These Problems
We use LLM-as-judge for automatic response comparison. This allows quickly assessing how well the new model matches the old one. For prompt adaptation, we wrote a PromptAdapter class that converts system prompts between providers and adds XML tags for Claude. The unified UnifiedLLMClient hides API differences and allows switching providers with a single line of code.
Our automated approach is 3 times faster than manual migration, and LLM-as-judge reduces testing time by 90%.
from anthropic import Anthropic
from openai import OpenAI
import json
import time
from typing import Callable
anthropic_client = Anthropic()
openai_client = OpenAI()
class LLMMigrationAnalyzer:
"""Analyzes compatibility and quality during migration"""
def compare_responses(
self,
test_cases: list[dict],
source_fn: Callable,
target_fn: Callable,
) -> dict:
"""Compares responses of two models on test cases"""
results = []
for case in test_cases:
source_response = source_fn(case["messages"], case.get("system"))
target_response = target_fn(case["messages"], case.get("system"))
# LLM-as-judge for quality assessment
quality_score = self.judge_quality(
case["messages"][-1]["content"],
source_response,
target_response,
)
results.append({
"input": case["messages"][-1]["content"],
"source": source_response[:200],
"target": target_response[:200],
"quality_score": quality_score,
"recommendation": "migrate" if quality_score >= 0.8 else "review",
})
return {
"total_cases": len(results),
"safe_to_migrate": len([r for r in results if r["recommendation"] == "migrate"]),
"needs_review": len([r for r in results if r["recommendation"] == "review"]),
"avg_quality": sum(r["quality_score"] for r in results) / len(results),
"cases": results,
}
def judge_quality(self, question: str, source: str, target: str) -> float:
"""Evaluates quality of target response relative to source"""
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": f"""Compare two AI responses to the same question.
Question: {question}
Response A: {source[:500]}
Response B: {target[:500]}
Rate Response B compared to A on a scale 0-1 where:
1.0 = B is better or equal to A
0.7 = B is slightly worse but acceptable
0.5 = B has notable quality degradation
0.0 = B is significantly worse
Return only a number."""
}],
temperature=0,
)
try:
return float(response.choices[0].message.content.strip())
except ValueError:
return 0.5
Prompt Adaptation During GPT to Claude Migration
class PromptAdapter:
"""Adapts prompts between providers"""
# Differences between models
GPT_TO_CLAUDE_RULES = {
# OpenAI uses messages array for system
# Claude uses separate system parameter
"system_prompt": "separate_parameter",
# Claude prefers XML tags for structuring
# GPT does not require special formatting
"prefer_xml_tags": True,
# Claude follows instructions with explicit constraints better
"explicit_constraints": True,
}
def adapt_system_prompt(self, gpt_system: str) -> str:
"""Adapts system prompt for Claude"""
response = anthropic_client.messages.create(
model="claude-haiku-4-5",
max_tokens=2048,
messages=[{
"role": "user",
"content": f"""Adapt this system prompt from OpenAI GPT to Anthropic Claude.
Adaptation rules:
- Preserve the main meaning and instructions
- Use XML tags for structuring (<instructions>, <constraints>, <format>)
- Claude follows concrete examples better, add them if needed
- Remove mentions of "GPT", "ChatGPT" if present
Original prompt:
{gpt_system}
Return only the adapted prompt."""
}]
)
return response.content[0].text
def adapt_function_tools(self, openai_tools: list) -> list:
"""Converts OpenAI tools to Claude tool_use format"""
claude_tools = []
for tool in openai_tools:
if tool.get("type") == "function":
func = tool["function"]
claude_tools.append({
"name": func["name"],
"description": func["description"],
"input_schema": func.get("parameters", {
"type": "object",
"properties": {}
})
})
return claude_tools
Abstraction Layer for Smooth Migration
from enum import Enum
class LLMProvider(str, Enum):
OPENAI = "openai"
ANTHROPIC = "anthropic"
OLLAMA = "ollama"
class UnifiedLLMClient:
"""Unified interface for all providers"""
def __init__(self, provider: LLMProvider, model: str):
self.provider = provider
self.model = model
def complete(self, messages: list[dict], system: str = "", **kwargs) -> str:
"""Unified method for all providers"""
if self.provider == LLMProvider.ANTHROPIC:
response = anthropic_client.messages.create(
model=self.model,
max_tokens=kwargs.get("max_tokens", 2048),
system=system,
messages=messages,
temperature=kwargs.get("temperature", 0.1),
)
return response.content[0].text
elif self.provider == LLMProvider.OPENAI:
all_messages = []
if system:
all_messages.append({"role": "system", "content": system})
all_messages.extend(messages)
response = openai_client.chat.completions.create(
model=self.model,
messages=all_messages,
max_tokens=kwargs.get("max_tokens", 2048),
temperature=kwargs.get("temperature", 0.1),
)
return response.choices[0].message.content
elif self.provider == LLMProvider.OLLAMA:
import requests
all_messages = []
if system:
all_messages.append({"role": "system", "content": system})
all_messages.extend(messages)
response = requests.post(
"http://localhost:11434/v1/chat/completions",
json={"model": self.model, "messages": all_messages}
)
return response.json()["choices"][0]["message"]["content"]
# Change provider — single line
client = UnifiedLLMClient(LLMProvider.ANTHROPIC, "claude-haiku-4-5")
# -> client = UnifiedLLMClient(LLMProvider.OPENAI, "gpt-4o-mini")
Why Direct Prompt Transfer Doesn't Work
OpenAI uses system message as part of the context; Claude uses a separate parameter with higher weight. If you simply copy the text, Claude may ignore some instructions due to lack of XML markup. Additionally, GPT and Claude interpret roles differently: in GPT you can specify "role": "system", while in Claude the system is set outside the message array. Without adaptation, you risk getting formal, templated responses or context loss.
How to Accelerate Migration Without Losing Quality
Our approach is automation through LLM-as-judge and PromptAdapter. We collect 50-100 real production requests, run them through both models, and evaluate quality. If the average score is below 0.8, we adapt prompts. This surfaces problematic cases in a day instead of a week of manual testing. For typical tasks, we use adaptation templates, speeding up the process 2-3 times.
Typical Migration Mistakes
- Copying system prompt without XML tags for Claude.
- Ignoring context window differences (truncating text without adaptation).
- Skipping testing of edge cases (long dialogues, tool calls).
- Omitting a fallback strategy for failures.
Step-by-Step Migration Process
- Collect test cases – Gather 50-100 real user queries from production logs.
- Run A/B comparison – Use LLM-as-judge to compare responses from old and new models.
- Adapt prompts and tools – Convert system prompts and function calls to target provider format.
- Deploy unified client – Implement UnifiedLLMClient with fallback support.
- Gradual rollout – Route 5% of traffic, monitor metrics, then increase to full deployment.
What's Included in the Service
| Stage | What We Do | Result |
|---|---|---|
| Analysis | Examine current architecture, collect 50-100 test requests | Compatibility report |
| Prompt Adaptation | Convert system prompts and tool calls | Adapted prompts tested on test cases |
| Client Development | Implement UnifiedLLMClient with fallback support | Unified interface for all providers |
| A/B Test | Route 5% of traffic to new model | Comparison of quality and cost metrics |
| Rollout | Gradually increase new model share to 100% | Stable operation on new LLM |
| Documentation and Training | Document provider switching process, train team | Documentation and workshop |
| Support | 2 weeks of post-migration monitoring | Quick resolution of any issues |
Timeline
- Compatibility analysis + testing: 1 week
- Prompt and tool adaptation: 1 week
- A/B test in production + rollout: 1-2 weeks
Migration Checklist
| Step | Action | Criticality |
|---|---|---|
| 1 | Collect 50-100 test requests from production | Required |
| 2 | Conduct A/B comparison via LLM-as-judge | Required |
| 3 | Adapt system prompts | Required |
| 4 | Convert format of tool calls | Required |
| 5 | Update error handling (different error codes) | Required |
| 6 | Set up retry/fallback | Recommended |
| 7 | Update cost monitoring | Recommended |
| 8 | A/B test in production (5% traffic) | Recommended |
We offer a free assessment of your project. We will analyze your current architecture and propose a migration plan tailored to your budget. With over 5 years of experience and 20+ completed projects, we guarantee quality preservation and support at all stages.







