Guide · Anthropic

Anthropic Python: Text Completions to the Messages API

The Text Completions API took one prompt string built from HUMAN_PROMPT and AI_PROMPT. The Messages API takes a list of messages and returns a list of content blocks. Version 1.0.0 of the Python SDK removed the old method entirely.

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=...) becomes client.messages.create(messages=[...]), a list of user and assistant turns instead of one string.
  • HUMAN_PROMPT and AI_PROMPT no longer exist. Roles carry that information.
  • max_tokens_to_sample is called max_tokens.
  • Instructions that apply to the whole conversation go in the top-level system parameter.
  • The reply is message.content, a list of typed blocks, not the completion string.
  • A natural stop is reported as end_turn. stop_sequence now means one of your own stop sequences was hit.
  • Streaming yields typed events (message_start, content_block_delta, message_stop and others), and the SDK adds a messages.stream() helper.

Migration steps

  1. 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, since messages.create() exists there too, and upgrade afterwards.

  2. 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_PROMPT and AI_PROMPT were 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 reads ANTHROPIC_API_KEY from the environment. Pick the model from Anthropic's current models list; claude-opus-5-5 is the id used in the docs' own examples.

  3. 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 assistant turns 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?"},
        ],
    )
  4. Read content blocks instead of completion

    message.content is a list. For a plain text request the first block is usually a text block, which is why content[0].text works in simple cases. Responses can also contain other block types, such as tool_use when you pass tools or thinking blocks, 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_tokens
  5. Update 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_sequence whenever 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 is end_turn, and stop_sequence only appears when one of your stop_sequences was emitted. The other values are tool_use, pause_turn, refusal and model_context_window_exceeded. An unchanged == "stop_sequence" check silently stops matching.

  6. 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 a content_block_start, one or more content_block_delta events and a content_block_stop, then message_delta and message_stop, with ping events possibly mixed in. Text arrives in deltas whose delta.type is text_delta; ignore event types you do not handle rather than failing on them.

  7. Drop sampling parameters you no longer need

    Completions code often set temperature, top_p or top_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 from messages.create(), so passing them is a TypeError. Remove them. If you are pinned to an older model that still honors them, the migration notes say to pass them through extra_body, for example extra_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 python

The 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 httpx package, which 1.x no longer uses, so they can silently stop intercepting. Call httpx2.alias_httpx() before anything imports httpx, or inject a fake client.
  • Assert on stop_reason in at least one test that lets the model finish naturally and one that hits max_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.

Sources