Claude Opus 5: Hands-On with Code Generation, Agent Orchestration, and Cost Analysis Anthropic quietly shipped Claude Opus 5 last week, offering roughly 33% lower cost than Opus 4 with improved code generation and tool-calling reliability. A developer who tested the model on a cross-border e-commerce pipeline found it consistently infers production concerns like caching, error boundaries, and type safety without explicit prompting. Anthropic quietly shipped Claude Opus 5 last week. Not a flashy announcement, but the numbers caught my attention: roughly 33% cheaper than Opus 4, better at code generation, and supposedly more reliable with tool calling. I run a small cross-border e-commerce operation where we process product data from dozens of suppliers, generate multi-language descriptions, and manage order workflows. AI is already part of that pipeline. So I spent a weekend porting some of our codebase to Opus 5 and comparing it against what we were using. This post covers three things: code generation quality, agent orchestration behavior, and whether the price cut actually changes how you'd design a system. The first test was practical. I needed a currency exchange microservice that caches rates, handles fallbacks, and exposes a clean API. No novel problem, but the kind of task where a model either saves you time or wastes it with hallucinated imports. I asked Opus 5 for a multi-file FastAPI project structure. Here's what it generated for the service layer: python app/services/rate.py import aiohttp from decimal import Decimal from app.cache import RateCache class RateService: def init self : self.cache = RateCache ttl=300 self.api url = "https://api.exchangerate.host/latest" async def get rate self, currency: str - Decimal | None: cached = self.cache.get currency if cached: return cached async with aiohttp.ClientSession as session: params = {"base": "USD", "symbols": currency} async with session.get self.api url, params=params, timeout=5 as resp: if resp.status == 200: data = await resp.json rate = Decimal str data "rates" currency self.cache.set currency, rate return rate return None It handled module separation well. The import paths were consistent across files, the cache layer was separated from the fetch logic, and error handling existed for missing currencies and timeouts. Not groundbreaking, but clean enough that I didn't need to restructure anything. The real surprise was the cache layer. Opus 4 often generated an in-memory dict with no TTL logic. Opus 5 added TTL-based expiration by default: python app/cache.py from dataclasses import dataclass from typing import Dict, Optional import time @dataclass class CacheEntry: value: Decimal expires at: float class RateCache: def init self, ttl: int = 300 : self. data: Dict str, CacheEntry = {} self. ttl = ttl def get self, key: str - Optional Decimal : entry = self. data.get key if entry and time.time < entry.expires at: return entry.value return None def set self, key: str, value: Decimal : self. data key = CacheEntry value, time.time + self. ttl The key takeaway: Opus 5 is noticeably better at inferring production concerns that you'd otherwise have to prompt for explicitly. It assumes you want caching, error boundaries, and type safety. This reduces iteration loops when scaffolding new services. The bigger question for me was tool calling. We run several automated agents that handle order reconciliation — fetching order data, checking inventory, updating tracking numbers. Tool-calling consistency has been the bottleneck, not model intelligence. I tested Opus 5 with a simple agent framework pattern: python from typing import TypedDict, List, Optional import json class ToolCall TypedDict : name: str arguments: dict class Agent: def init self, model: str = "claude-opus-5" : self.model = model self.tools = {} self.history: List dict = def register tool self, name: str, fn: callable, description: str : self.tools name = {"fn": fn, "desc": description} def run self, task: str - str: plan = self. plan task results = for step in plan: tool = self.tools.get step "tool" if tool: try: result = tool