Skip to main content

CodeContests에서 Claude 4, Codex, Gemini 2.5 Pro 비교 테스트

Claude 4 Sonnet와 Opus를 직접 검증해보기 이 글은 AI로 번역되었습니다. 오역이 있을 경우 댓글로 알려주세요
Created on September 12|Last edited on September 12
Anthropic의 Claude Opus 4와 Sonnet 4는 코딩과 추론, 그리고 개발자 워크플로에 매끄럽게 통합되는 데 초점을 맞춘 차세대 대규모 언어 모델이다. Claude 제품군이 쌓아 온 강력한 성능을 기반으로, 이들 모델은 OpenAI의 codex-mini와 Google의 Gemini 2.5 Pro와 같은 유력 모델들과 나란히 공개되었으며, 엔지니어와 소프트웨어 팀을 위한 필수 도구가 되는 것을 목표로 한다.
이 글에서는 일관된 프로그래매틱 벤치마크와 실시간 코드 실행을 통해 Claude Opus 4와 Sonnet 4가 다양한 실무형 코딩 작업에서 어떤 성능을 보이는지, 역량과 신뢰성을 어떻게 측정할 수 있는지 살펴본다. 또한 OpenAI와 Google의 최신 모델과 결과를 비교함으로써, 최신 Claude 모델이 현대 개발 요구에 어떻게 부합하는지—특히 코드 품질, 문제 해결, 효율성이 실제로 중요한 상황에서—명확하고 최신의 관점을 제공하는 것을 목표로 한다.


목차



Claude 4의 주요 변화

Claude 4는 투명성과 안전성에서의 Anthropic의 강점을 바탕으로 AI 추론, 코드 생성, 에이전트형 워크플로우에서 큰 도약을 이뤘다. Claude 4 제품군에는 코딩과 복잡한 작업에서 새로운 최첨단을 제시하는 Claude Opus 4와, 속도와 지능, 정밀도가 크게 향상된 Claude Sonnet 4가 포함된다.
강력한 신규 모델이 두 가지 있습니다:
  • Claude Opus 4Anthropic의 현존 최상위 AI로, 난이도 높은 코딩, 다단계 추론, 대규모 에이전트 워크플로에서 탁월한 성능을 발휘합니다. 기존 벤치마크를 능가하며 소프트웨어 엔지니어링과 연구 분야에서 최고 수준의 성과를 제공합니다.
  • Claude Sonnet 4Sonnet 3.7에서 크게 도약하여 최상급 추론 능력, 개선된 지시 사항 준수, 더욱 강력한 코드 합성을 제공하며, 일상 사용에 적합한 더 빠르고 실용적인 응답을 제공합니다.
  • 도구 사용 + 확장 추론(베타):
두 모델 모두 이제 단계별 추론 과정에서 외부 도구(계산기, 코드 실행기, 웹 검색, 커스텀 API 등)를 사용할 수 있으며, 중간에 “생각”을 위해 멈췄다가 도구를 호출하고, 다시 답변을 이어서 구성한다.
  • 와 함께 교차형 추론Claude는 투명하고 감사 가능한 워크플로를 위해 내부 사고, 도구 호출, 최종 출력 사이를 번갈아가며 전환할 수 있습니다.
  • 병렬 도구 사용과 강화된 메모리Claude 4는 도구를 병렬로 실행할 수 있으며, 허용되는 경우 여러 턴에 걸쳐 핵심 사실과 문맥을 “기억”할 수 있습니다. 파일 접근이 가능할 때 Opus 4는 자체 메모리 파일을 생성하고 업데이트하여, 긴 에이전트 실행 동안 더 나은 문맥 유지와 연속성을 제공합니다.
  • API 업그레이드: 코드 실행, 유연한 파일 API, MCP 커넥터, 최대 1시간까지의 프롬프트 캐싱 등 새로운 API 기능에 접근하여 더 강력하고 지속 가능한 AI 워크플로를 구축할 수 있습니다.
  • 더 나은 안전성, 더 적은 허점: 두 모델 모두 이전 세대와 비교해 지름길/허점 행동이 65% 감소하여, 에이전트형 작업과 코드 수정의 신뢰성을 높였습니다.

LLM 벤치마크: 지능 능력을 가늠하는 북극성일까?

LLM 평가에서 점점 커지는 문제는, 명시적으로 해당 벤치마크로 학습되지 않았더라도 모델이 벤치마크 테스트 세트에 “과적합”된 것처럼 보인다는 점입니다. 이는 학습 중에 본 정답을 외워버리는 전통적 의미의 과적합이 아닙니다. 대신, LLM의 사전 학습 방식으로 인해 대규모로 발생하는 데이터 누출에 더 가깝습니다.
GSM8K, HumanEval, MMLU 같은 대다수의 인기 벤치마크는 수년 동안 온라인에 유통되어 왔습니다. GitHub, 학술 논문, 블로그 글, 튜토리얼 등에 널리 공개되어 있죠. LLM이 공개 인터넷을 대규모로 크롤링한 데이터로 사전 학습될 때, 이러한 벤치마크의 일부나 심지어 전체 사본을 접했을 가능성이 큽니다. 즉, 평가 시점에 벤치마크가 파인튜닝에서 제외되었다 하더라도, 모델이 이미 해당 과제를 “알고” 있을 수 있다는 뜻입니다.
이는 강한 일반화 능력이 있는 듯한 착시를 만듭니다. 모델이 실제로 문제를 풀어서가 아니라, 형식을 알아보고 유사한 표현을 기억하거나 정답 분포를 암기했기 때문에 높은 점수를 받는 것입니다. 그래서 어떤 모델들은 해당 테스트 세트로 특별히 학습되지 않았어도 좋은 성능을 보일 수 있습니다. 공개 데이터에서 매우 유사한(혹은 동일한) 질문을 충분히 많이 보아, 일반화된 것처럼 보이게 속일 수 있기 때문입니다.
이는 특히 여러 모델이 유사한 말뭉치로 학습될 때 벤치마크 점수를 신뢰하기 어렵게 만듭니다. 사전 학습과 평가 사이의 경계가 너무 흐릿하고, 벤치마크가 재사용될수록 그 가치는 침식됩니다. 추론이나 일반화에 대한 중대한 주장은, 진정으로 보지 못한 비공개 평가로 뒷받침되지 않는 한 주의해서 받아들여야 합니다. 따라서 우리가 이후에 수행할 벤치마크가 진정한 일반화를 반영한다고 보증할 수는 없습니다. 공개 벤치마크는 점점 더 포화 상태에 이르고 있으며(명시적으로 그들로 학습하지 않았다 하더라도, 사전 학습 중에 모델이 접했을 가능성이 높습니다).
그렇다고 해서 벤치마크가 무용하다는 뜻은 아닙니다. 벤치마크는 모델을 비교하고, 시간에 따른 변화를 추적하며, 특정 능력을 스트레스 테스트하는 공통 기준점을 제공합니다. 다만 ‘진실’이 아니라 진단 도구로 다루어야 합니다. 벤치마크에서 높은 점수를 받았다고 해서 모델이 과제를 이해한다는 의미는 아닙니다. 예전에 봤던 내용을 그대로 재현했을 가능성도 있습니다.

우리의 벤치마크: CodeContests

우리는 CodeContests에서 Gemini 2.5 Pro, OpenAI’s codex, 그리고 Claude 4 Sonnet과 Opus 4를 실행해 실제 경쟁 문제를 누가 가장 잘 해결하는지 확인하겠습니다. 이 컬렉션은 Codeforces, AtCoder, CodeChef 같은 플랫폼에서 수집한 실제 경쟁 프로그래밍 문제 10,000개 이상을 모았습니다. 각 문제에는 자연어 설명, 여러 공개 및 비공개 테스트 케이스, 태그와 난이도 등 메타데이터가 포함되어 있습니다.
CodeContests는 다양한 문제 유형과 엄격한 평가 설정 덕분에 코드 생성 모델의 벤치마크로 널리 사용됩니다. 이 문제들은 LLM을 염두에 두고 설계되지 않았으며, 그럴듯한 코드가 아니라 명확한 추론, 정밀한 구현, 테스트 케이스에서의 실제 정답성을 요구합니다.
이번 비교에서는 공개 테스트 스플릿의 일부만 사용하여 각 모델이 보지 못한 실제 문제들을 접하도록 하겠습니다. 이를 통해 Gemini 2.5 Pro, Codex, 그리고 Claude 4 Sonnet과 Opus가 실제 경쟁 프로그래밍 과제에서 얼마나 잘 수행하는지 투명하고 현실적인 모습을 제시합니다.

지표로서의 코드 정답성

서로 다른 언어 모델을 공정하고 자동화된 방식으로 평가하기 위해, 맞춤형 경량 코드 실행 프레임워크를 구축했습니다. 각 경쟁 프로그래밍 문제에 대해 모델에는 자연어로 된 문제 설명만 제공하고, 해법으로서 Python 함수 생성을 요청합니다. 이 프레임워크는 Gemini 2.5 Pro, Codex, 그리고 Claude 4 Sonnet과 Opus를 호출하는 방식을 표준화하여, 모든 코드 한 줄과 모든 테스트가 직접 비교 가능하도록 동등한 조건을 보장합니다.
내 설정의 핵심 특징은 LLM을 단지 코드 생성에만 쓰지 않고, 예시 입력 문자열을 올바른 함수 호출로 변환하거나 실행을 위한 데이터 포맷팅과 같은 지원 작업에도 활용한다는 점입니다. 이를 통해 예제 입력을 Python 호출로 매핑하는 등 모든 접착 작업을 모든 문제와 모든 모델에 걸쳐 일관되게 수행할 수 있습니다.
생성된 코드를 공개 테스트 케이스에 실행한 뒤, 출력이 기대 정답과 일치하는지 비교하는 과정에는 LLM을 활용합니다. 이렇게 하면 출력 검증이 훨씬 견고해집니다. LLM은 형식상의 겉보기 차이나 사소한 부동소수점 오차가 있더라도 의미적으로 동등한 결과를 인식할 수 있어, 일반적인 문자열 비교로는 놓칠 수 있는 경우까지 잡아낼 수 있습니다.
함수 호출과 정답 검증을 LLM으로 자동화함으로써 평가 전 과정을 반복 가능하고 일관되게 만들었고, 무엇보다 실제 정답성—즉 모델이 생성한 코드가 실제 테스트 케이스를 통과하는지—에 집중하도록 했습니다. 핵심 초점은 언제나 그럴듯해 보이는 코드가 아니라 실제 성능에 있습니다.

실험 1: 최적의 생각 토큰 수는 얼마인가요??

첫 번째 실험의 목표는 기본적인 질문에 답하는 것입니다. 코드 문제를 풀도록 프롬프트할 때, Claude Sonnet 4에게 내부 추론을 얼마나 요구해야 할까? Claude는 “생각” 과정에 대해 세밀한 제어를 제공합니다. 즉, budget_tokens 값을 지정하면 최종 답을 생성하기 전에 Claude가 내부 추론(소위 “생각 블록”)에 사용할 수 있는 토큰의 최대치를 설정할 수 있습니다.
이를 검증하기 위해 표준 설정(명시적 생각 없음)부터 24,000 토큰까지 다양한 생각 예산을 시험했습니다. 각 구성마다 CodeContests의 동일한 경쟁 프로그래밍 문제 세트에 Claude를 실행했습니다. 프레임워크는 각 문제의 자연어 설명을 모델에 전달하고 해법을 수집한 뒤, 실제 테스트 케이스에서 실행하여 정답성과 지연 시간을 모두 측정합니다.
다양한 구성에서의 결과를 기록해 보면, Claude의 “생각 예산”을 늘리는 것이 코드 정확도와 응답 시간에 어떤 영향을 미치는지 확인할 수 있습니다. 내부 성찰의 여유를 더 주면 더 나은 코드를 쓰는 데 도움이 될까요, 아니면 단지 속도만 늦출까요? 이 실험은 그 트레이드오프를 상세히 도식화하도록 설계되었으며, 이를 통해 최적의 성능을 위해 추론 깊이를 조정할 수 있습니다.
평가에 사용한 코드는 다음과 같습니다:
import os
import sys
import time
import subprocess
from datasets import load_dataset
from litellm import completion as oai_completion
from anthropic import Anthropic
from google import genai
import weave
from weave.flow.eval_imperative import EvaluationLogger
from google.genai import types
import re

weave.init("codecontests_evalv2")

os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "your_api_key")
OAIKEY = os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "your_api_key")
CLAUDE_KEY = os.getenv("CLAUDE_API_KEY", "your_api_key")

GENAI_KEY = os.getenv("GOOGLE_API_KEY", "your_api_key")
from openai import OpenAI
client = OpenAI(api_key=OAIKEY)


anthropic_client = Anthropic(api_key=CLAUDE_KEY)
gemini_client = genai.Client(api_key=GENAI_KEY)

def clean_llm_code_block(text):
cleaned_text = text.replace("```python", "").replace("```", "").strip()
code_blocks = re.findall(r"(def solve\(.*?)(?=^def |\Z)", cleaned_text, re.DOTALL | re.MULTILINE)
source_text = code_blocks[-1] if code_blocks else cleaned_text
prompt = (
"Given the following response from a language model, extract ONLY the valid Python code for the function. "
"Do not include any explanations, text, or formatting fences. Only the code.\n\n"
f"Response:\n{source_text}\n\n"
"Return ONLY the Python code, including any necessary imports:"
)
response = oai_completion(
model="openai/gpt-4o-2024-08-06",
messages=[{"role": "user", "content": prompt}],
temperature=0.0,
)
gpt4o_code = response["choices"][0]["message"]["content"]
gpt4o_code = gpt4o_code.replace("```python", "").replace("```", "").strip()
return gpt4o_code



@weave.op()
def generate_completion(model: str, prompt: str) -> str:
# Codex models (openai/codex-*, openai/codex-mini-latest, etc)
if model.startswith("openai/codex-"):
# Use the .responses.create API as per the latest OpenAI SDK
codex_model = model.replace("openai/", "")
# You can optionally add custom instructions (here, we use a neutral persona)
response = client.responses.create(
model=codex_model,
instructions="You are a helpful, accurate Python coding assistant.",
input=prompt
)
return response.output_text.strip()
# General OpenAI chat-completions
elif model.startswith("openai/"):
from litellm import completion as oai_completion
response = oai_completion(
model=model,
messages=[{"role": "user", "content": prompt}],
reasoning_effort="low",
)
return response["choices"][0]["message"]["content"].strip()

# Anthropic Claude (API unchanged)
elif model.startswith("anthropic/"):
response = anthropic_client.messages.create(
model=model.replace("anthropic/", ""),
max_tokens=8000,
thinking={"type": "enabled", "budget_tokens": 4000},
messages=[{"role": "user", "content": prompt}],
)
for block in response.content:
if block.type == "text":
return block.text.strip()
return "[No Claude response]"

# Gemini (API unchanged)
elif model.startswith("gemini/"):
result = gemini_client.models.generate_content(
model=model.replace("gemini/", ""),
config=types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(thinking_budget=4000)
),
contents=[prompt]
)
return result.text.strip() if result.text else "[No Gemini response]"

else:
raise ValueError(f"Unsupported model: {model}")




def ask_llm_for_function_implementation(description: str, model: str) -> str:
prompt = (
f"Write a Python3 function named `solve` with typed input arguments for this problem -- eg the solve function should take arguments to handle different test cases:\n\n"
f"{description.strip()}\n\n"
"Return only a valid Python function -- no special packages that arent commonly used and NO MAIN function, no if __name__ == __main__....., JUST write the function -- that returns the result. No comments, no explanations."
f"HOWEVER, you still need to include necessary imports for libraries"
f"IF you do not include the right imports, the code will not be executable, and your response will be judged as incorrect!"
)
return clean_llm_code_block(generate_completion(model, prompt))

@weave.op
def ask_llm_for_function_call(code: str, raw_input: str, model: str) -> str:
prompt = (
"You're given a Python function and a single input string. "
"Format it into a valid Python function call using only standard types.\n\n"
f"Function:\n{code}\n\n"
f"Input:\n{raw_input.strip()}\n\n"
"Return ONLY a valid function call (e.g., solve(3, 5)) WITH NO 'def' "
)
response = oai_completion(
model="openai/gpt-4o-2024-08-06",
messages=[{"role": "user", "content": prompt}],
temperature=0.0,
)
content = response["choices"][0]["message"]["content"]
content = content.replace("```python", "").replace("```", "").strip()
return content

def compare_output_with_llm(expected: str, actual: str, model: str) -> bool:
prompt = (
f"Expected output: {expected.strip()}\n"
f"Actual output: {actual.strip()}\n\n"
"Are these outputs equivalent? Eg ignore minor formatting errors etc, we are just looking for overall correctness in the output Reply YES or NO."
)
response = oai_completion(
model="openai/gpt-4o-2024-08-06",
messages=[{"role": "user", "content": prompt}],
temperature=0.0,
)
res = 'YES' in str(response["choices"][0]["message"]["content"]).upper()
return res

def run_code_and_call_function(code: str, function_call: str, timeout=10):
full_code = code + f"\n\nprint({function_call})"
try:
start = time.time()
result = subprocess.run(
[sys.executable, "-c", full_code],
capture_output=True,
text=True,
timeout=timeout
)
latency = time.time() - start
return result.stdout.strip(), result.stderr.strip(), latency
except subprocess.TimeoutExpired:
return "", "Execution timed out.", timeout
except Exception as e:
return "", str(e), 0.0

def ask_model_for_pip_command(error_msg):
prompt = (
"Given this Python error:\n\n"
+ error_msg +
"\n\nWrite the pip install command needed to fix it. Only return the command, e.g.:\n"
"pip install requests"
)
return generate_completion("openai/gpt-4o-2024-08-06", prompt)

def run_pip_install(pip_command):
print(f"Running: {pip_command}")
try:
result = subprocess.run(
pip_command.split(),
capture_output=True,
text=True,
timeout=180
)
print(result.stdout.strip())
if result.stderr:
print(result.stderr.strip())
except Exception as e:
print(f"pip install failed: {e}")

def evaluate_model_on_code_contests(model_name: str):
print(f"\n\nRunning evaluation for model: {model_name}\n")
ds = load_dataset("deepmind/code_contests", split="test", streaming=True)
ds = list(ds.take(31))

eval_logger = EvaluationLogger(
model=model_name.replace("-", "_").replace("/", "_").replace(".", "_"),
dataset="code_contests_test"
)
all_latencies = []

for i in range(30):
row = ds[i]
description = row["description"]
raw_inputs = row["public_tests"]["input"]
expected_outputs = row["public_tests"]["output"]

try:
code = ask_llm_for_function_implementation(description, model=model_name)
print(f"\n=== Task {row['name']} ===", flush=True)
all_passed = True
task_latencies = []
results_lst, expected_lst = [], []

for j, raw_input in enumerate(raw_inputs):
expected = expected_outputs[j] if j < len(expected_outputs) else ""

try:
function_call = ask_llm_for_function_call(code, raw_input, model=model_name)
result, error, latency = run_code_and_call_function(code, function_call)
if latency < 99:
task_latencies.append(latency)

if error:
print(f"[{j}] Runtime error: {error}")
if "ModuleNotFoundError" in error:
pip_cmd = ask_model_for_pip_command(error)
run_pip_install(pip_cmd)
# Re-run once after pip install
result, error, latency = run_code_and_call_function(code, function_call)
task_latencies.append(latency)
if error:
print(f"[{j}] Retry failed: {error}")
all_passed = False
continue
else:
all_passed = False
continue

is_correct = compare_output_with_llm(expected, result, model="openai/gpt-4o-2024-08-06")
results_lst.append(result)
expected_lst.append(expected)
if not is_correct:
all_passed = False
print(f"[{j}] input: {raw_input.strip()} → output: {result} | expected: {expected.strip()} | PASS: {is_correct} | latency: {latency:.2f}s")

except Exception as inner:
print(f"[{j}] Inner error: {repr(inner)}")
all_passed = False

task_avg_latency = sum(task_latencies) / len(task_latencies) if len(task_latencies) > 0 else 0.0
all_latencies.extend(task_latencies)

prediction_log = eval_logger.log_prediction(
inputs={"description": description},
output={'code': code, 'execution_result': results_lst, 'expected_execution_result': expected_lst}
)
prediction_log.log_score("correctness", all_passed)
prediction_log.log_score("code_latency", task_avg_latency)
prediction_log.finish()

except Exception as e:
print(f"[{i}] Top-level failure: {repr(e)}")
prediction_log = eval_logger.log_prediction(
inputs={"description": description},
output=str(e)
)
prediction_log.log_score("correctness", False)
prediction_log.finish()

avg_latency = sum(all_latencies) / len(all_latencies) if all_latencies else 0.0
eval_logger.log_summary({"avg_code_latency": avg_latency})
print(f"Evaluation complete for {model_name}. View in Weave UI.")

# ---- RUN FOR ALL TARGET MODELS ----

evaluate_model_on_code_contests("gemini/gemini-2.5-pro-preview-05-06")
evaluate_model_on_code_contests("anthropic/claude-sonnet-4-20250514")
evaluate_model_on_code_contests("openai/codex-mini-latest")
evaluate_model_on_code_contests("anthropic/claude-opus-4-20250514")
이 Python 스크립트는 경쟁 프로그래밍 작업을 대상으로 Gemini, Claude, OpenAI의 Codex를 자동으로 평가하는 시스템을 구성합니다. 먼저 DeepMind Code Contests 데이터셋에서 문제 설명을 가져와 각 LLM에 Python solve 함수를 생성하도록 요청합니다. 그런 다음 코드가 생성된 Python을 지능적으로 정제하고 추출하며, 제공된 테스트 케이스에 대해 실행할 수 있도록 필요한 함수 호출을 구성합니다.
이 프레임워크는 견고한 오류 처리로 설계되어 있어, 만약 다음과 같은 상황을 만나면 ModuleNotFoundError 실행 중에는 GPT-4o에게 올바른 pip install 명령을 제안해 달라고 요청하고, 누락된 패키지 설치를 시도한 뒤 코드를 재시도하기도 합니다. 중요한 점은 단순한 문자열 비교 대신 LLM을 사용해 실제 출력과 기대 출력의 의미적 동등성을 비교한다는 것입니다. 이를 통해 사소한 서식 차이에 관대해지고, 진정한 정답성에 더욱 집중할 수 있습니다.
결과를 시각화하기 위해 Weave Evaluations를 사용해 평가 지표를 기록했습니다. 이를 통해 모델이 생성한 원시 코드, 테스트 입력, 출력, 오류, 그리고 평균 지연 시간 같은 성능 지표를 모두 수집합니다. 덕분에 실패 사례를 쉽게 점검하고, 모델 전반의 추세를 파악하며, 모든 결과와 버그가 추적 가능해집니다. 이는 모델과 벤치마크가 함께 발전하는 상황에서 특히 유용합니다. 저는 Weave의 신규 EvaluationLogger 이를 위해, 평가를 계측하는 유연한 방법을 제공합니다. 이어서 EvaluationLogger엄격한 형식에 묶일 필요는 없습니다. 데이터셋을 수동으로 순회하고, 원하는 방식으로 모델을 호출하며, 예측과 점수를 생성되는 대로 기록할 수 있습니다. 덕분에 기존 파이프라인이나 맞춤형 평가 루프에 모든 코드를 새로 작성하지 않고도 Weave를 손쉽게 끼워 넣을 수 있습니다.
Weave 내에서 표시된 결과는 다음과 같습니다:

내부 “생각” 할당량의 변화가 코드 생성 성능에 미치는 영향을 평가하기 위해 30개 샘플을 테스트했습니다. 표시되는 “총 토큰” 지표에는 전체 생각 토큰이 포함되지 않는 점에 유의해야 합니다. Claude 4 모델이 내부 사고 과정을 요약 형태로만 제공하는 경우가 있기 때문입니다. 흥미롭게도 생각 예산이 없는 모델의 정답률은 36.7%로, 1024 토큰 생각 예산 구성의 26.7%보다 높았습니다. 다만 생각 예산을 더 늘리자 성능도 함께 향상되었습니다. 4000 토큰 예산 모델은 40.0%의 정답률을 기록했으며, 8000 토큰 예산에서도 이 수준이 유지되었습니다. 특히 12000 토큰 생각 예산으로 설정한 모델은 53.3%로 가장 높은 정답률을 보여 의미 있는 개선을 보였습니다. 이는 최소한의 사고만 허용하면 때로는 성능을 저해할 수 있지만, 충분한 사고 할당은 해결 품질을 크게 끌어올린다는 점을 시사합니다.

실험 2: Claude Sonnet 4 vs Claude Opus 4 vs Codex vs Gemini 2.5 Pro

두 번째 실험에서는 CodeContests 테스트 세트에서 동일한 30개 경쟁 프로그래밍 문제 배치를 사용해 Claude 4 Sonnet, Claude 4 Opus, OpenAI의 신규 Codex 모델, Gemini 2.5 Pro를 직접 비교했습니다. 각 모델에는 문제의 자연어 설명만 제공되며, 해답으로 단일한 독립형 Python 함수 하나를 반환해야 합니다.
각 샘플 입력마다 올바른 호출 시그니처를 자동으로 생성하고, 격리된 환경에서 후보 코드를 실행합니다. 그런 다음 결과 출력은 견고한 LLM 기반 의미적 동등성 검사를 사용해 기대되는 정답과 대조합니다. 이 접근법은 사소한 서식 불일치에 평가가 흔들리지 않도록 보장하고, 실제 정답성에 초점을 맞춥니다.
이렇게 엄격하고 동일한 조건에서 세 모델을 모두 실행해 보면, 각 시스템이 실제 현업 수준의 코딩 과제를 어떻게 처리하는지 선명한 그림을 얻을 수 있습니다. 이는 코드가 그럴듯해 보이는지뿐 아니라, 실제 문제 해결 능력과 구현 역량까지 함께 측정합니다.
평가에 사용한 코드는 다음과 같습니다:
import os
import sys
import time
import subprocess
from datasets import load_dataset
from litellm import completion as oai_completion
from anthropic import Anthropic
from google import genai
import weave
from weave.flow.eval_imperative import EvaluationLogger
from google.genai import types
import re

weave.init("codecontests_evalv2")

os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "your_api_key")
OAIKEY = os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "your_api_key")
CLAUDE_KEY = os.getenv("CLAUDE_API_KEY", "your_api_key")

GENAI_KEY = os.getenv("GOOGLE_API_KEY", "your_api_key")


from openai import OpenAI
client = OpenAI(api_key=OAIKEY)

anthropic_client = Anthropic(api_key=CLAUDE_KEY)
gemini_client = genai.Client(api_key=GENAI_KEY)

def clean_llm_code_block(text):
cleaned_text = text.replace("```python", "").replace("```", "").strip()
code_blocks = re.findall(r"(def solve\(.*?)(?=^def |\Z)", cleaned_text, re.DOTALL | re.MULTILINE)
source_text = code_blocks[-1] if code_blocks else cleaned_text
prompt = (
"Given the following response from a language model, extract ONLY the valid Python code for the function. "
"Do not include any explanations, text, or formatting fences. Only the code.\n\n"
f"Response:\n{source_text}\n\n"
"Return ONLY the Python code, including any necessary imports:"
)
response = oai_completion(
model="openai/gpt-4o-2024-08-06",
messages=[{"role": "user", "content": prompt}],
temperature=0.0,
)
gpt4o_code = response["choices"][0]["message"]["content"]
gpt4o_code = gpt4o_code.replace("```python", "").replace("```", "").strip()
return gpt4o_code


@weave.op()
def generate_completion(model: str, prompt: str, thinking_budget=None, streaming=True) -> str:
response_text = ""
if model.startswith("anthropic/"):
# Calculate correct total max_tokens
if thinking_budget and int(thinking_budget) > 0:
max_tokens = 8000 + int(thinking_budget)
else:
max_tokens = 8000

create_kwargs = dict(
model=model.replace("anthropic/", ""),
max_tokens=max_tokens,
messages=[{"role": "user", "content": prompt}],
)

# Only provide thinking if budget is positive and not None
if thinking_budget and int(thinking_budget) > 0:
create_kwargs['thinking'] = {"type": "enabled", "budget_tokens": int(thinking_budget)}

client = anthropic_client

if streaming:
with client.messages.stream(**create_kwargs) as stream:
for event in stream:
if event.type == "content_block_start":
block_type = event.content_block.type
if block_type == "thinking":
print("\n[THINKING]: ", end="", flush=True)
elif block_type == "text":
print("\n[RESPONSE]: ", end="", flush=True)
elif event.type == "content_block_delta":
d = event.delta
if getattr(d, "type", None) == "thinking_delta":
print(d.thinking, end="", flush=True)
elif getattr(d, "type", None) == "text_delta":
print(d.text, end="", flush=True)
response_text += d.text
elif event.type == "content_block_stop":
print()
else:
response = client.messages.create(**create_kwargs)
for block in response.content:
if block.type == "thinking" and getattr(block, "thinking", "").strip():
print("\n[THINKING]:", block.thinking.strip(), flush=True)
elif block.type == "text" and getattr(block, "text", "").strip():
print("\n[RESPONSE]:", block.text.strip(), flush=True)
response_text += block.text.strip()
return str(response_text)
else:
raise ValueError(f"Unsupported model: {model}")


def ask_llm_for_function_implementation(description: str, model: str, thinking_budget=None) -> str:
prompt = (
f"Write a Python3 function named `solve` with typed input arguments for this problem -- eg the solve function should take arguments to handle different test cases:\n\n"
f"{description.strip()}\n\n"
"Return only a valid Python function -- no special packages that arent commonly used and NO MAIN function, no if __name__ == __main__....., JUST write the function -- that returns the result. No comments, no explanations."
f"HOWEVER, you still need to include necessary imports for libraries"
f"IF you do not include the right imports, the code will not be executable, and your response will be judged as incorrect!"
)
return clean_llm_code_block(generate_completion(model, prompt, thinking_budget=thinking_budget))

@weave.op
def ask_llm_for_function_call(code: str, raw_input: str, model: str, thinking_budget=None) -> str:
prompt = (
"You're given a Python function and a single input string. "
"Format it into a valid Python function call using only standard types.\n\n"
f"Function:\n{code}\n\n"
f"Input:\n{raw_input.strip()}\n\n"
"Return ONLY a valid function call (e.g., solve(3, 5)) WITH NO 'def' "
)
response = oai_completion(
model="openai/gpt-4o-2024-08-06",
messages=[{"role": "user", "content": prompt}],
temperature=0.0,
)
content = response["choices"][0]["message"]["content"]
content = content.replace("```python", "").replace("```", "").strip()
return content

def compare_output_with_llm(expected: str, actual: str, model: str) -> bool:
prompt = (
f"Expected output: {expected.strip()}\n"
f"Actual output: {actual.strip()}\n\n"
"Are these outputs equivalent? Eg ignore minor formatting errors etc, we are just looking for overall correctness in the output Reply YES or NO."
)
response = oai_completion(
model="openai/gpt-4o-2024-08-06",
messages=[{"role": "user", "content": prompt}],
temperature=0.0,
)
res = 'YES' in str(response["choices"][0]["message"]["content"]).upper()
return res

def run_code_and_call_function(code: str, function_call: str, timeout=10):
full_code = code + f"\n\nprint({function_call})"
try:
start = time.time()
result = subprocess.run(
[sys.executable, "-c", full_code],
capture_output=True,
text=True,
timeout=timeout
)
latency = time.time() - start
return result.stdout.strip(), result.stderr.strip(), latency
except subprocess.TimeoutExpired:
return "", "Execution timed out.", timeout
except Exception as e:
return "", str(e), 0.0

def ask_model_for_pip_command(error_msg):
prompt = (
"Given this Python error:\n\n"
+ error_msg +
"\n\nWrite the pip install command needed to fix it. Only return the command, e.g.:\n"
"pip install requests"
)
# In this context, no "thinking" is likely needed; you could propagate a thinking_budget if you want
return generate_completion("anthropic/claude-sonnet-4-20250514", prompt, thinking_budget=None)

def run_pip_install(pip_command):
print(f"Running: {pip_command}")
try:
result = subprocess.run(
pip_command.split(),
capture_output=True,
text=True,
timeout=180
)
print(result.stdout.strip())
if result.stderr:
print(result.stderr.strip())
except Exception as e:
print(f"pip install failed: {e}")

def evaluate_model_on_code_contests(model_name: str, thinking_budget=None):
tb_str = "nothinking" if not thinking_budget or int(thinking_budget) == 0 else f"tb{thinking_budget}"
print(f"\n\nRunning evaluation for model: {model_name} | thinking_budget={tb_str}\n")
ds = load_dataset("deepmind/code_contests", split="test", streaming=True)
ds = list(ds.take(31))

eval_logger = EvaluationLogger(
model=f"{model_name.replace('-', '_').replace('/', '_').replace('.', '_')}_{tb_str}",
dataset="code_contests_test"
)
all_latencies = []

for i in range(30):
row = ds[i]
description = row["description"]
raw_inputs = row["public_tests"]["input"]
expected_outputs = row["public_tests"]["output"]

try:
code = ask_llm_for_function_implementation(description, model=model_name, thinking_budget=thinking_budget)
print(f"\n=== Task {row['name']} ===", flush=True)
all_passed = True
task_latencies = []
results_lst, expected_lst = [], []

for j, raw_input in enumerate(raw_inputs):
expected = expected_outputs[j] if j < len(expected_outputs) else ""

try:
function_call = ask_llm_for_function_call(code, raw_input, model=model_name, thinking_budget=thinking_budget)
result, error, latency = run_code_and_call_function(code, function_call)
if latency < 99:
task_latencies.append(latency)

if error:
print(f"[{j}] Runtime error: {error}")
if "ModuleNotFoundError" in error:
pip_cmd = ask_model_for_pip_command(error)
run_pip_install(pip_cmd)
# Re-run once after pip install
result, error, latency = run_code_and_call_function(code, function_call)
task_latencies.append(latency)
if error:
print(f"[{j}] Retry failed: {error}")
all_passed = False
continue
else:
all_passed = False
continue

is_correct = compare_output_with_llm(expected, result, model="openai/gpt-4o-2024-08-06")
results_lst.append(result)
expected_lst.append(expected)
if not is_correct:
all_passed = False
print(f"[{j}] input: {raw_input.strip()} → output: {result} | expected: {expected.strip()} | PASS: {is_correct} | latency: {latency:.2f}s")

except Exception as inner:
print(f"[{j}] Inner error: {repr(inner)}")
all_passed = False

task_avg_latency = sum(task_latencies) / len(task_latencies) if len(task_latencies) > 0 else 0.0
all_latencies.extend(task_latencies)

prediction_log = eval_logger.log_prediction(
inputs={"description": description},
output={'code': code, 'execution_result': results_lst, 'expected_execution_result': expected_lst}
)
prediction_log.log_score("correctness", all_passed)
prediction_log.log_score("code_latency", task_avg_latency)
prediction_log.finish()

except Exception as e:
print(f"[{i}] Top-level failure: {repr(e)}")
prediction_log = eval_logger.log_prediction(
inputs={"description": description},
output=str(e)
)
prediction_log.log_score("correctness", False)
prediction_log.finish()

avg_latency = sum(all_latencies) / len(all_latencies) if all_latencies else 0.0
eval_logger.log_summary({"avg_code_latency": avg_latency})
print(f"Evaluation complete for {model_name} (thinking_budget={tb_str}). View in Weave UI.")

# ---- RUN FOR CLAUDE/SONNET4 FOR MULTIPLE THINKING BUDGETS INCLUDING "NO THINKING" ----
thinking_budgets = [None, 1024, 4000, 8000, 12000 ]
for tb in thinking_budgets:
evaluate_model_on_code_contests("anthropic/claude-sonnet-4-20250514", thinking_budget=tb)


평가 결과는 다음과 같습니다:

Codex는 “생각” 기능이 없음에도 불구하고 63%의 정답률로 가장 높은 정확도를 기록하며 다른 모델들을 앞질렀습니다 (적어도 공개적으로 알려진 기능은 없으며, 모델 API도 노력 정도를 지정하는 매개변수를 받지 않습니다.). Claude Sonnet와 Claude Opus의 정답률은 각각 43%와 40%입니다. Gemini 2.5 Pro의 정답률은 36%입니다. 전반적으로 Claude Sonnet 4가 Claude Opus 4를 앞선 점이 흥미로웠지만, 이번 평가는 Claude와 Gemini 모델에 대해 생각 토큰을 4,000개로 제한했기 때문에, 더 큰 생각 예산에서 이들 모델이 어떻게 확장되는지 지켜보는 것도 의미 있을 것입니다.

Weave 비교 뷰

Weave의 비교 뷰는 여러 코딩 모델 간의 추론 차이를 명확하게 시각화하는 데 특히 가치가 있습니다. 각 모델의 출력을 나란히 표시해 주기 때문에, 정답성과 논리적 일관성의 차이를 즉시 식별할 수 있습니다. 이 직관적인 인터페이스를 통해 어떤 모델은 실패하고 다른 모델은 성공하는지 그 이유를 빠르게 짚어낼 수 있습니다.

이러한 인사이트는 코딩 성능을 효과적으로 분석하고 최적화하는 데 도움을 줍니다. 결과뿐 아니라 그 이면의 코딩 스타일까지 조명함으로써, Weave의 비교 뷰는 각 모델의 추론 능력을 평가하고 개선하는 과정을 단순화합니다.

결론

CodeContests 벤치마크에서 Claude 4 Sonnet, Claude 4 Opus, OpenAI의 Codex, Gemini 2.5 Pro를 평가한 결과는 실제 코딩 과제를 처리하는 데 있어 각 모델의 역량에 대한 유의미한 인사이트를 제공합니다. 결과에 따르면 Codex가 가장 높은 정답률을 기록하며 다른 모델들을 앞질렀습니다. 한편, 이 평가는 Claude 계열 모델에서 생각 예산의 중요성도 부각했는데, 생각 예산을 12,000 토큰으로 늘렸을 때 정답률이 크게 향상되는 것으로 나타났습니다.
이들 모델의 비교는, 특히 코딩 과제 맥락에서 대규모 언어 모델을 평가하는 일이 얼마나 복잡한지 잘 보여줍니다. 커스텀 코드 실행 프레임워크와 LLM 기반 의미적 동등성 검사를 활용함으로써 공정하고 견고한 평가 절차를 보장합니다. 또한 결과는 벤치마크가 모델 비교에 유용하지만, 데이터 누출과 과적합 가능성 때문에 신중히 다뤄져야 함을 시사합니다.
전반적으로 이번 평가는 코딩 과제에서 현행 대규모 언어 모델의 강점과 한계를 이해하는 데 기여합니다. 이러한 모델들의 성능을 분석함으로써, 개발자들은 접근 방식을 정교화하여 모델 성능을 최적화하고 해결책의 품질을 향상시킬 수 있습니다.


이 글은 AI로 번역되었습니다. 오역이 의심되는 부분이 있으면 댓글로 알려주세요. 원문 보고서는 아래 링크에서 확인할 수 있습니다: 원문 보고서 보기