{"slug": "i-used-the-openai-sdk-and-claude-answered-heres-why", "title": "I Used the OpenAI SDK—and Claude Answered. Here’s Why.", "summary": "An engineer demonstrated that the OpenAI Python SDK can be used to call Anthropic's Claude model by pointing the base_url to Anthropic's compatibility endpoint. The post explains the conceptual separation between SDK, API endpoint, serving layer, and inference runtime, emphasizing that the SDK package name does not determine which model answers.", "body_md": "You install the OpenAI Python package.\n\nYou import `OpenAI`\n\n.\n\nYou call `client.chat.completions.create()`\n\n.\n\nAnd **Claude** answers.\n\n``` python\nimport os\nfrom openai import OpenAI\n\nclient = OpenAI(\n    api_key=os.environ[\"ANTHROPIC_API_KEY\"],\n    base_url=\"https://api.anthropic.com/v1/\",\n)\n\nresponse = client.chat.completions.create(\n    model=\"claude-sonnet-4-6\",\n    messages=[\n        {\"role\": \"user\", \"content\": \"Explain this in one sentence.\"}\n    ],\n)\n\nprint(response.choices[0].message.content)\n```\n\nThat looks wrong the first time you see it.\n\nIf the SDK says `openai`\n\n, shouldn't an OpenAI model answer?\n\nNo—and the reason matters far beyond this one code sample.\n\nThree independent choices are hiding in that snippet:\n\nThe package name is not the model name.\n\nIn the example above:\n\n```\nOpenAI Python SDK\n        ↓ speaks OpenAI-style HTTP\nAnthropic compatibility endpoint\n        ↓ maps the request\nClaude Sonnet\n        ↓ result is mapped back\nOpenAI-shaped response\n        ↓ parsed by the SDK\nresponse.choices[0].message.content\n```\n\nAnthropic officially provides this compatibility layer for testing and comparing Claude with existing OpenAI integrations. It is not the recommended production path for most Claude-first applications, for reasons we will get to shortly.\n\nBut first, let us separate the layers developers accidentally compress into the word “AI.”\n\nThe useful mental model is not “SDK → model.” It is:\n\n```\n┌───────────────────────────────────────────────┐\n│ 1. Application + SDK                         │\n│    Builds a request and parses a response    │\n├───────────────────────────────────────────────┤\n│ 2. API endpoint / gateway                    │\n│    Auth, routing, policy, protocol mapping   │\n├───────────────────────────────────────────────┤\n│ 3. Serving layer                             │\n│    Scheduling, batching, streaming, metrics  │\n├───────────────────────────────────────────────┤\n│ 4. Inference runtime + model                  │\n│    Prompt → tokens → generation → tokens     │\n└───────────────────────────────────────────────┘\n```\n\nThese layers may live in one program, several containers, or multiple companies' infrastructure. The boundaries are conceptual, but the responsibilities are different.\n\nAn SDK usually gives you:\n\nWithout an SDK, the same job can be done with an HTTP client:\n\n``` python\nimport os\nimport httpx\n\nresponse = httpx.post(\n    \"https://api.anthropic.com/v1/messages\",\n    headers={\n        \"x-api-key\": os.environ[\"ANTHROPIC_API_KEY\"],\n        \"anthropic-version\": \"2023-06-01\",\n        \"content-type\": \"application/json\",\n    },\n    json={\n        \"model\": \"claude-sonnet-4-6\",\n        \"max_tokens\": 100,\n        \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}],\n    },\n)\n\ndata = response.json()\nprint(data[\"content\"][0][\"text\"])\n```\n\nThe native Anthropic response uses `content[]`\n\n. The OpenAI-style response uses `choices[]`\n\n.\n\nThose shapes are API contracts. Neither is the natural output format of a neural network.\n\nConsider these clients:\n\n```\n# OpenAI\nOpenAI(\n    api_key=os.environ[\"OPENAI_API_KEY\"],\n    base_url=\"https://api.openai.com/v1\",\n)\n\n# Anthropic's OpenAI compatibility layer\nOpenAI(\n    api_key=os.environ[\"ANTHROPIC_API_KEY\"],\n    base_url=\"https://api.anthropic.com/v1/\",\n)\n\n# A local Ollama server\nOpenAI(\n    api_key=\"ollama\",  # required by the client; ignored locally\n    base_url=\"http://localhost:11434/v1/\",\n)\n```\n\nThe calling style barely changes, but the request crosses three completely different trust, billing, latency, and data boundaries.\n\nThe `base_url`\n\ncan point to:\n\nThis is why “we use the OpenAI SDK” tells an architect almost nothing about where prompts go.\n\nThe next questions should be:\n\n`base_url`\n\n?At the lowest useful level, a language model works with numbers.\n\nThe prompt is formatted and tokenized. The model produces logits. A generation strategy selects new token IDs. Those IDs are decoded into text.\n\nFor example, Hugging Face Transformers documents that `generate()`\n\nreturns token sequences—or a richer internal `ModelOutput`\n\nwhen requested:\n\n```\ngenerated_ids = model.generate(**model_inputs, max_new_tokens=50)\ntext = tokenizer.batch_decode(\n    generated_ids,\n    skip_special_tokens=True,\n)[0]\n```\n\nThere is no universal neural-network law requiring the result to contain:\n\n```\n{\n  \"choices\": [],\n  \"finish_reason\": \"stop\",\n  \"usage\": {\n    \"prompt_tokens\": 10,\n    \"completion_tokens\": 5\n  }\n}\n```\n\nThat public JSON shape is assembled by software around the generation runtime.\n\nA serving system may also produce internal text, token IDs, finish metadata, timing information, cache statistics, and scheduler state. The important distinction is not “the engine only returns text.” The important distinction is:\n\nProvider JSON is a network contract created by the serving/API layer—not an intrinsic property of the model weights.\n\nProjects such as vLLM make this visible: the inference machinery returns internal request outputs, while the OpenAI-compatible server exposes endpoints with OpenAI-style schemas.\n\nThe `model`\n\nfield is a request to the service, not a Python import.\n\nThe endpoint decides how to interpret it.\n\n```\nresponse = client.chat.completions.create(\n    model=\"anthropic/claude-sonnet-4.6\",\n    messages=[{\"role\": \"user\", \"content\": \"Hello\"}],\n)\n```\n\nA gateway might:\n\nSo even the model string is not always the complete deployment identity. In production, log the resolved provider, model version, request ID, and routing decision whenever the platform exposes them.\n\nThis is where a convenient prototype becomes a quiet production bug.\n\nTwo services can support `/v1/chat/completions`\n\nand still disagree on:\n\nAnthropic documents several concrete limitations in its OpenAI compatibility layer:\n\n`strict`\n\nfor function calling is ignored;`response_format`\n\n, `logprobs`\n\n, and several other fields are ignored;Some unsupported fields are silently ignored.\n\nThat last behavior is more dangerous than a clean error. Your code can compile, your request can return `200`\n\n, and your assumption can still be false.\n\nOllama uses equally careful wording: it supports **parts** of the OpenAI API. vLLM documents its own list of supported and additional parameters.\n\nCompatibility is a spectrum, not a boolean.\n\nSuppose this prototype works:\n\n```\nclient = OpenAI(\n    api_key=os.environ[\"GATEWAY_API_KEY\"],\n    base_url=os.environ[\"LLM_BASE_URL\"],\n)\n```\n\nChanging an environment variable may be enough for basic chat completion.\n\nThen production adds:\n\nNow the lowest common denominator begins to cost you.\n\nThe abstraction was not free. You deferred the translation work to a gateway—and accepted its fidelity limits.\n\n| Situation | Sensible default | Main trade-off |\n|---|---|---|\n| Claude-first production app | Native Anthropic SDK | Best Claude feature coverage; tighter vendor coupling |\n| OpenAI-first production app | Native OpenAI SDK | Best OpenAI feature coverage; tighter vendor coupling |\n| Model evaluation | OpenAI-compatible surface or gateway | Fast switching; feature comparisons may be incomplete |\n| Mostly portable text generation | Common gateway contract | Simple integration; lowest-common-denominator risk |\n| Heavy tools, streaming, or multimodal use | Native provider adapters behind your own interface | More code; explicit and testable behavior |\n| Local development | Ollama or vLLM compatibility endpoint | Convenient; confirm exactly which features are supported |\n\nFor a serious multi-provider application, I prefer a small internal interface whose implementation uses native provider SDKs.\n\nFor example:\n\n``` python\nfrom typing import Protocol\n\nclass TextModel(Protocol):\n    def generate(self, prompt: str) -> str: ...\n\nclass ClaudeModel:\n    def generate(self, prompt: str) -> str:\n        # Native Anthropic SDK implementation\n        ...\n\nclass OpenAIModel:\n    def generate(self, prompt: str) -> str:\n        # Native OpenAI SDK implementation\n        ...\n```\n\nThis is more work than changing `base_url`\n\n, but it makes the lossy parts visible. Your domain code depends on your contract, while provider-specific capabilities remain available inside each adapter.\n\nIf you choose a universal gateway instead, create contract tests for every feature you rely on:\n\n```\n✓ plain text\n✓ streaming\n✓ tool call arguments\n✓ strict structured output\n✓ image input\n✓ stop reasons\n✓ usage accounting\n✓ retryable error classification\n```\n\nDo not test only whether the first “Hello” request succeeds.\n\nWhen someone says:\n\n“We use the OpenAI SDK.”\n\nTranslate it to:\n\n“This code uses a client designed around an OpenAI API shape.”\n\nIt does **not** automatically mean:\n\nRemember the chain:\n\n```\nSDK → API contract → endpoint/router → serving runtime → model\n```\n\nThe SDK speaks a protocol.\n\nThe endpoint receives and may route the request.\n\nThe serving stack runs—or delegates—the generation work.\n\nThe model generates token probabilities.\n\nOnce those responsibilities are separate in your head, “OpenAI SDK calling Claude” stops looking like magic. It becomes what it always was: one client speaking a compatible network contract to an endpoint that knows how to reach Claude.\n\nWhat broke first when you switched LLM providers: **streaming, tool calls, structured output, or usage accounting?**\n\nShare the failure mode in the comments. Those edge cases are where “compatible” gets interesting.\n\n*Disclosure: This article is based on the author's original technical material and subject-matter knowledge. AI was used to help restructure the article, edit the English, and create the cover image. The technical claims and cited sources were reviewed before publication.*", "url": "https://wpnews.pro/news/i-used-the-openai-sdk-and-claude-answered-heres-why", "canonical_source": "https://dev.to/aekanun/i-used-the-openai-sdk-and-claude-answered-heres-why-1l8o", "published_at": "2026-07-25 15:14:22+00:00", "updated_at": "2026-07-25 15:34:06.366216+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "developer-tools"], "entities": ["OpenAI", "Anthropic", "Claude", "Ollama", "Hugging Face"], "alternates": {"html": "https://wpnews.pro/news/i-used-the-openai-sdk-and-claude-answered-heres-why", "markdown": "https://wpnews.pro/news/i-used-the-openai-sdk-and-claude-answered-heres-why.md", "text": "https://wpnews.pro/news/i-used-the-openai-sdk-and-claude-answered-heres-why.txt", "jsonld": "https://wpnews.pro/news/i-used-the-openai-sdk-and-claude-answered-heres-why.jsonld"}}