Anthropic marks Text Completions (POST /v1/complete) as deprecated and says future models and features will not be compatible with it. On the Python side the deadline is concrete: anthropic 1.0.0, released on August 20, 2026, removed client.completions.create(), the Completion types and the anthropic.HUMAN_PROMPT and anthropic.AI_PROMPT constants. If you upgrade the package without migrating, the import of those constants fails before any request is sent. This guide follows the SDK's v1 migration notes and the Claude API docs, linked at the end.
What changed
client.completions.create(prompt=...)becomesclient.messages.create(messages=[...]), a list ofuserandassistantturns instead of one string.HUMAN_PROMPTandAI_PROMPTno longer exist. Roles carry that information.max_tokens_to_sampleis calledmax_tokens.- Instructions that apply to the whole conversation go in the top-level
systemparameter. - The reply is
message.content, a list of typed blocks, not thecompletionstring. - A natural stop is reported as
end_turn.stop_sequencenow means one of your own stop sequences was hit. - Streaming yields typed events (
message_start,content_block_delta,message_stopand others), and the SDK adds amessages.stream()helper.
Migration steps
Upgrade the SDK
pip install --upgrade "anthropic>=1,<2"1.x needs Python 3.10 or later and moves the HTTP layer from httpx to httpx2. If you only pass plain options such as
timeout=30.0, nothing else changes for you; the testing section covers mocks. You can do the call-site migration below on a 0.x release first, sincemessages.create()exists there too, and upgrade afterwards.Replace the prompt string with messages
Before
from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT client = Anthropic() completion = client.completions.create( model="claude-2.1", max_tokens_to_sample=300, prompt=f"{HUMAN_PROMPT} Summarize this ticket: {ticket}{AI_PROMPT}", ) print(completion.completion)After
from anthropic import Anthropic client = Anthropic() message = client.messages.create( model="claude-opus-5-5", max_tokens=300, messages=[{"role": "user", "content": f"Summarize this ticket: {ticket}"}], ) print(message.content[0].text)HUMAN_PROMPTandAI_PROMPTwere the\n\nHuman:and\n\nAssistant:markers the old endpoint required. In the Messages API the role field replaces them, so strip them out of the text rather than translating them. The client still readsANTHROPIC_API_KEYfrom the environment. Pick the model from Anthropic's current models list;claude-opus-5-5is the id used in the docs' own examples.Move instructions to system, and history to turns
Code that builds a conversation by concatenating strings becomes a list. The Messages API is stateless: you send the full history every time, and earlier
assistantturns can be ones you wrote yourself.Before
prompt = ( f"{RULES}" f"{HUMAN_PROMPT} Hello, Claude{AI_PROMPT} Hello!" f"{HUMAN_PROMPT} Can you describe LLMs to me?{AI_PROMPT}" ) completion = client.completions.create( model="claude-2.1", max_tokens_to_sample=1024, prompt=prompt )After
message = client.messages.create( model="claude-opus-5-5", max_tokens=1024, system=RULES, messages=[ {"role": "user", "content": "Hello, Claude"}, {"role": "assistant", "content": "Hello!"}, {"role": "user", "content": "Can you describe LLMs to me?"}, ], )Read content blocks instead of completion
message.contentis a list. For a plain text request the first block is usually atextblock, which is whycontent[0].textworks in simple cases. Responses can also contain other block types, such astool_usewhen you pass tools orthinkingblocks, so code that must not break should collect the text blocks explicitly:text = "".join(block.text for block in message.content if block.type == "text") tokens_in, tokens_out = message.usage.input_tokens, message.usage.output_tokensUpdate stop reason checks
Before
if completion.stop_reason == "stop_sequence": done = True elif completion.stop_reason == "max_tokens": log.warning("truncated")After
if message.stop_reason == "end_turn": done = True elif message.stop_reason == "max_tokens": log.warning("truncated") elif message.stop_reason == "refusal": handle_refusal(message)Text Completions reported
stop_sequencewhenever the model reached a stop sequence, including the built-in\n\nHuman:, so most code treated it as "finished". In the Messages API a natural finish isend_turn, andstop_sequenceonly appears when one of yourstop_sequenceswas emitted. The other values aretool_use,pause_turn,refusalandmodel_context_window_exceeded. An unchanged== "stop_sequence"check silently stops matching.Rewrite streaming
Before
stream = client.completions.create(**params, stream=True) for data in stream: print(data.completion, end="")After
with client.messages.stream( model="claude-opus-5-5", max_tokens=1024, messages=[{"role": "user", "content": "Hello"}], ) as stream: for text in stream.text_stream: print(text, end="", flush=True) final = stream.get_final_message()If you call
messages.create(stream=True)instead, you get raw events:message_start, then for each content block acontent_block_start, one or morecontent_block_deltaevents and acontent_block_stop, thenmessage_deltaandmessage_stop, withpingevents possibly mixed in. Text arrives in deltas whosedelta.typeistext_delta; ignore event types you do not handle rather than failing on them.Drop sampling parameters you no longer need
Completions code often set
temperature,top_portop_k. Anthropic's docs say models after Claude Opus 4.6 reject non-default values with a 400 error, and anthropic 1.x removed the three parameters frommessages.create(), so passing them is aTypeError. Remove them. If you are pinned to an older model that still honors them, the migration notes say to pass them throughextra_body, for exampleextra_body={"temperature": 0.2}.
How to find every affected call
Search for the call, the constants and the response reads separately, because they often live in different files. ast-grep matches the syntax tree, so calls split across lines are found:
sg run --pattern '$CLIENT.completions.create($$$ARGS)' --lang python
sg run --pattern 'HUMAN_PROMPT' --lang python
sg run --pattern 'AI_PROMPT' --lang python
sg run --pattern '$RES.completion' --lang python
sg run --pattern '$RES.stop_reason == "stop_sequence"' --lang python
sg run --pattern '$CLIENT.messages.create($$$ARGS)' --lang pythonThe first pattern also matches OpenAI's client.completions.create, so check which client each hit uses. The last one finds Messages calls that may still pass temperature, top_p or top_k. Prompt templates stored outside Python (YAML, text files) will not show up here; grep for Human: and Assistant: too.
Testing the change
- Run pyright or mypy after upgrading. The SDK's migration notes call a type checker a good checklist: removed methods, parameters and constants all show up as errors.
- Fix HTTP mocks. respx, pytest-httpx and vcrpy patch the
httpxpackage, which 1.x no longer uses, so they can silently stop intercepting. Callhttpx2.alias_httpx()before anything importshttpx, or inject a fake client. - Assert on
stop_reasonin at least one test that lets the model finish naturally and one that hitsmax_tokens. - Run one real streamed request in staging and confirm partial text arrives before the end of the response.
Automating it
Anthropic does not ship a standalone codemod. Its v1 migration notes suggest that Claude Code users run /claude-api upgrade python in the project and review the diff, and they recommend a type checker to catch what is left. Either way you end up reviewing an edit made by a model or by hand.
Versyka finds every call site deterministically with ast-grep patterns like the ones above, rewrites them together with the code that reads completion and stop_reason, then runs your repository's own type check and tests in a disposable sandbox. It opens a pull request only if those checks pass.
Was this guide helpful?
Check your own repository
Paste your package.json, requirements.txt or pyproject.toml into the free upgrade check to see which API SDKs are behind, with no account. Or let Versyka run this migration on one repository and open a pull request that has already passed your tests.