Python ChatGPT API and DeepSeek API: Straight‑to‑the‑Point Guide πŸπŸ€–

gui commited 2 days ago · 🐍 Python AI

ChatGPT is the future and not knowing its capabilities will limit your ability to produce high quality code faster than your peers.

No fluff β€” just working answers and bite‑sized code samples.

πŸ€” Can I actually use ChatGPT in Python?

Yes! Integrating ChatGPT with Python is simpler than cooking instant noodles 🍜. All you need is to pip install openai, your API key, and a few lines of Python code. Let’s do this!

🀩 Is ChatGPT Python API free?

No. Even if you subscribe to ChatGPT Plus/Pro, API usage is billed separately.

OpenAI’s API runs on a pay-as-you-go model, so you can begin experimenting for just a few dollars. If you're just learning you can start with as little as $5 to get started.

πŸ’Έ Python ChatGPT API Pricing

The prices vary by the model utilized and it's based per 1M tokens.

As of the time of this writing, consider these three examples using popular models o1, 4o and gpt-image-1 (to create that cool Studio Ghibli Photos):

Alias Model $ Input Token $ Cache Input $ Output Token
gpt-4o gpt-4o-2024-08-06 $2.50 $1.25 $10.00
o1 o1-2024-12-17 $15.00 $7.50 $60.00
gpt-image-1 gpt-image-1 $10.00 $2.50 $40.00

Prices might have gone up (or down - who knows?), so make sure to evaluate the Official ChatGPT Pricing Page.

🧩 What's ChatGPT Input and Output tokens?

Tokens are how ChatGPT reads and writes text. Instead of full words, it breaks everything into small chunks called tokens β€” like pieces of words, punctuation, or spaces.

You pay for both the tokens you send (input) and the ones you get back (output).

Text Token Count
Hello 1 token
Hello, world! 4 tokens (Hello, ,, world, !)
I love Python. 4 tokens (I, love, Python, .)

Don't be fooled. 1 word != 1 token because 1 token is about 4 characters of English text and there are words that are counted differently due to their complexity.

Word Example Token Count Breakdown
unbelievable un, believable β†’ 2 tokens
extraordinary extra, ordinary β†’ 2 tokens
internationalization international, ization β†’ 2 tokens
transportation trans, port, ation β†’ 3 tokens
misunderstanding mis, under, standing β†’ 3 tokens
counterproductive counter, pro, duct, ive β†’ 4 tokens
disproportionately dis, pro, portion, ately β†’ 4 tokens

And you don't even have to trust me, you can go straight to OpenAI Tokenizer and check it for yourself:

πŸ’° Caching ChatGPT Tokens to save money

Being practical, how can I optimize my tokens so I pay less?

Well, you could shorten prompts and messages, after all we learned that fewer tokens = lower cost but that's not easy.

Instead of putting effort into picking the right tokens (or words), you can try to use caching as much as possible to your advantage.

When you use ChatGPT API you're automatically using the caching capability. The API caches the longest prefix of a prompt recently used. After the first call you get 5–10 minutes to reuse it.

Prompt Caching
Prompts can be cached to save you some money
Request # Prompt Token Count
#1 You're a Python Expert: How to print hello world? 11 Tokens - No cache
#2 You're a Python Expert: How to sum two numbers? 11 Tokens, 7 Tokens Cached
#3 (After ~10 min of inactivity) You're a Python Expert: How to sum two numbers? 11 Tokens - No cache

Hitting caches would save you 50% of the current model pricing. (e.g. $2.5 regular -> $1.25 for cached).

You can check the usage when using the API so you can evaluate if the prompt worked as you expected. Let's see it later.

Don't forget you also pay for output tokens which you can't really control.

πŸ”‘ Creating a ChatGPT API Token (If you don't have one yet)

If you already have created your account you can skip this section.

Just give a cool name to your organization (either "Personal" or "Hobbies" should be fine).

Create Org
Step 1 - Create a new org
Generate API Key
Step 2 - Generate your key
Copy the token
Step 3 - Get your key
Pay
Step 4 - Pay to use ChatGPT API

Pay for it. If you're just experimenting, I recommend paying only $5 and sticking to model gpt-4.1-nano which is the cheapest.

How to create my ChatGPT API Token

Assuming you already have an account.

We can start by visiting OpenAI's Platform API Keys Page (Not the regular ChatGPT page).

Click on Create new secret key and submit it:

Create new API Key
Creating a ChatGPT API Key

πŸ§‘β€πŸ’» ChatGPT API Example using responses.create

To get you started you must install openai by running: pip install openai. I'm also installing rich to display the data nicely.

import typing as t
from openai import OpenAI
from rich import print

API_KEY: t.Final = "sk-proj-[REDACTED]"
MODEL: t.Final = "gpt-4.1-nano"  # Cheapest: $0.10 Input / $0.40 Output


class PythonExpert:
    def __init__(self):
        self.client = OpenAI(api_key=API_KEY)

    def ask_the_expert(self, question: str):
        response = self.client.responses.create(
            model=MODEL,
            instructions="You're a Python expert. Answer the question as best you can.",
            input=question,
        )

        return response


def main():
    expert = PythonExpert()

    question = 'How do I print "Hello, world!" in Python?'
    response = expert.ask_the_expert(question)
    answer = response.output_text

    print(f"[yellow]Question:[/yellow] {question}")
    print(f"\tAnswer: {answer}")


if __name__ == "__main__":
    main()

Note we're using client.responses.create. This is a recent release from 2025, and it supports background execution, web search, file search, memorize history, and interact with GUI.

You might decide to stick to the old client.chat.completions.create() if you want to keep the history and context locally.

πŸ§‘β€πŸ’» How to measure ChatGPT API Token Caching?

Let's say you want to measure your prompts to see how much caching you can get to optimize costs.

ChatGPT Caching only works for prompts with > 1,024 tokens. For our example I’m going to provide an extremely long instruction and try to be as verbose as possible:

import typing as t
from openai import OpenAI
from rich import print
from rich.console import Console
from rich.table import Table as RichTable
from textwrap import dedent


API_KEY: t.Final = "sk-proj-[REDACTED]"
MODEL: t.Final = "gpt-4.1-nano"  # πŸ‘ˆ Cheapest: $0.10 Input / $0.40 Output


class PythonExpert:
    def __init__(self):
        self.client = OpenAI(api_key=API_KEY)

    # NOTE: πŸ‘‡ Since we're simulating token caching, we need to ensure it has at least 1,024 tokens.
    # Part of this initial prompt boilerplate will ALSO be cached.
    BASE_PROMPT_BULLSHIT = dedent(
        """
    ─────────────────────────  PYTHON EXPERT SYSTEM REFERENCE GUIDE  ─────────────────────────

    SECTION 1  - Code Style (PEP 8)
    β–Έ Prefer *snake_case* for variables and functions, *PascalCase* for classes, and UPPER_SNAKE_CASE for module -level constants.
    β–Έ Keep lines ≀ 79 chars; wrap long expressions with implied line -continuations inside (), [] or {}.
    β–Έ Imports: standard lib β–Έ third -party β–Έ local, each block alphabetised; never use wildcard imports.
    β–Έ Use f -strings for interpolation; reserve `%` formatting for logging -style placeholders.

    SECTION 2  - Typing & Static Analysis
    β–Έ Add *type hints* (PEP 484) to every public function: def square(n: int | float) -> int | float: …
    β–Έ Avoid `Any`; prefer Protocols and generics for flexible APIs.
    β–Έ Run *mypy* or *pyright* in CI; treat *warnings as errors* to prevent regressions.
    β–Έ Use *typing -extensions* for back -ports of upcoming features (e.g. TypeAliasType).

    SECTION 3  - Performance & Profiling
    β–Έ Use built -ins and std -lib (sum, max, heapq) before reaching for numpy or pandas; C -optimised code often beats naive C -extensions.
    β–Έ Profile first!  `python  -m cProfile  -o stats.prof main.py` + *snakeviz* to visualise hotspots.
    β–Έ Favour list -comprehensions over explicit loops where readability permits; avoid premature micro -optimisation.
    β–Έ For numeric hotspots consider `numba` or Cython; for I/O hot paths, use buffering and async.

    SECTION 4  - Concurrency & Parallelism
    β–Έ *asyncio* excels at I/O -bound workloads: await network, file, or DB calls without blocking the event -loop.
    β–Έ For CPU -bound tasks use `concurrent.futures.ProcessPoolExecutor` or *multiprocessing*; the GIL limits pure threads.
    β–Έ Shield long awaitables with `asyncio.to_thread` in 3.9+ when you need to run a sync function without freezing awaitables.
    β–Έ Never share mutable state across processes without proper IPC (queues, managers, shared memory).

    SECTION 5  - Packaging & Distribution
    β–Έ Adopt *pyproject.toml*; specify build -system (`[build -system] requires = ["setuptools>=64", "wheel"]`).
    β–Έ `python  -m build` produces sdist + wheel; upload via *twine* to TestPyPI first.
    β–Έ Use *semantic -versioning*: MAJOR β†’ breaking, MINOR β†’ features, PATCH β†’ fixes.
    β–Έ Provide rich metadata (classifiers, project -urls) so pip search surfaces your project.

    SECTION 6  - Testing Philosophy
    β–Έ Prefer *pytest*; write small, deterministic testsβ€”no sleeps or network calls.
    β–Έ Isolate side -effects with fixtures + tmp_path; parametrize happy -path and edge cases.
    β–Έ Aim for behaviour over implementation: changing internals should not break tests as long as public contract holds.
    β–Έ Track coverage but don't chase 100 %; guard against critical regressions instead.

        SECTION 7 - Debugging & Logging
    β–Έ Insert `breakpoint()` (Python 3.7+) to drop into pdb without imports; use `pdbpp` for nicer colours & sticky mode.
    β–Έ Configure *logging* early: level via env var, write JSON logs in production, colourised human -friendly logs locally.
    β–Έ Never log secrets; scrub tokens/IP addresses with custom filters or structlog processors.
    β–Έ Prefer structured logging over free -text for easier log aggregation and querying.

    SECTION 8 - Security Best Practices
    β–Έ Load secrets from the environment or a secrets -managerβ€”never hard -code keys.
    β–Έ Pin dependencies with hashes (`pip -tools`, `poetry lock --no-update`); audit with `pip-audit` or GitHub Dependabot.
    β–Έ Validate user input; distrust deserialisation (yaml.load, pickle).  Use `json.loads` or pydantic models instead.
    β–Έ Keep Python patched (security -fix releases); run containers as non -root and drop capabilities.

    SECTION 9 - Data Classes & Validation
    β–Έ Use `@dataclass(slots=True, frozen=True)` for lightweight value objects; benefits: immutability & memory savings.
    β–Έ For external data, model with *pydantic* or *attrs* for runtime validation and parsing.
    β–Έ Document JSON schema; version breaking changes.  Provide migration scripts between schema versions.
    β–Έ Convert between domain models and persistence DTOs to keep layers isolated.

    SECTION 10 - Command -line Interfaces (CLI)
    β–Έ Prefer *typer* (built on click) for ergonomic CLIs with auto -generated help and type -hints.
    β–Έ Support `--version`, `--help`, exit codes (0 success, non -zero failure).  Provide rich `stderr` messages for errors.
    β–Έ Package entry -points under `[project.scripts]` in *pyproject.toml* so `pipx` users can install system -wide.
    β–Έ Test CLI commands with `pytest` + `capsys` or *click.testing*'s runner.

    SECTION 11 - Configuration Management
    β–Έ Hierarchy: CLI args β–Ά env vars β–Ά `.env` file β–Ά config file β–Ά defaults.  Later overrides earlier.
    β–Έ Use *dynaconf* or `pydantic.Settings` for 12 -factor -style config loading.
    β–Έ Keep secrets out of git; supply sample env files for local dev.
    β–Έ Provide schema validation so a broken config fails fast at startup.

    SECTION 12 - Documentation & Docstrings
    β–Έ Write *Google -style* or *NumPy -style* docstrings; include type hints, parameter descriptions, return values, raises.
    β–Έ Generate docs with *mkdocs -material* or *Sphinx* + *autodoc*; host on GitHub Pages.
    β–Έ Keep examples runnable: embed doctests or use *pytest -doctestplus*.
    β–Έ Treat docs as code: review PRs, run spell -checkers (codespell), and enforce link rot checks.

    ───────────────────────────────────────────────────────────────────────────────────────────
    """
    )

    def ask_the_expert(self, question: str):
        response = self.client.responses.create(
            model=MODEL,
            instructions=self.BASE_PROMPT_BULLSHIT,
            input=question,
        )

        return response

# πŸ‘‡ Organize & Output it nicely
def _create_table() -> RichTable:
    table = RichTable(title="Token Usage Summary")
    table.add_column("[bold]Prompt[/bold]")
    table.add_column("[bold]Input Tokens[/bold]", justify="right")
    table.add_column("[bold]Cached Tokens[/bold]", justify="right")
    table.add_column("[bold]Output Tokens[/bold]", justify="right")
    table.add_section()

    return table


QUESTIONS = [
    'How do I print "Hello, world!" in Python, and why is `print` a function rather than a statement?',
    # πŸ‘† Verbose
    # πŸ‘‡ Somewhat simple to simulate cache hitting
    'How do I print "Hello, world!" in Python?',
]


def main():
    expert = PythonExpert()
    table = _create_table()
    console = Console()

    for question in QUESTIONS:
        response = expert.ask_the_expert(question)

        answer = response.output_text
        input_tokens = response.usage.input_tokens
        cached = response.usage.input_tokens_details.cached_tokens
        output_tokens = response.usage.output_tokens

        print(f"[yellow]Question:[/yellow] {question}")
        print(f"\tAnswer: {answer}")

        table.add_row(
            question,
            str(input_tokens),
            str(cached),
            str(output_tokens),
        )

    console.print(table)


if __name__ == "__main__":
    main()
Token Output Usage
Token Usage through API Example

Since we know that gpt-4.1-nano charges $0.10 / $0.025 / $0.40 we can calculate:

Prompt # Uncached Input Tokens Cached Input Tokens Output Tokens Estimated Cost
#1 1,369 0 227 $0.0002277
#2 107 1,262 30 $0.00005926

πŸ“ Structuring ChatGPT responses as JSON Models

When you're creating your tool you probably want a structured response to ensure the output has a known format.

You can achieve that by using Pydantic and ChatGPT's client.responses.parse.

  1. Define your expected response model with Pydantic;
  2. Update to use client.responses.parse
  3. Update instructions to mention what you expect to be parsed
  4. Pass your model as the text_format keyword argument
  5. Get response.output_parsed
import typing as t
from openai import OpenAI
from rich import print
from pydantic import BaseModel

API_KEY: t.Final = "sk-proj-[REDACTED]"
MODEL: t.Final = "gpt-4.1-nano"  # Cheapest: $0.10 Input / $0.40 Output


# πŸ‘‡ Define the expected output model
class ExpertResponse(BaseModel):
    explanation: str
    example_code: str


class PythonExpert:
    def __init__(self):
        self.client = OpenAI(api_key=API_KEY)

    def ask_the_expert(self, question: str) -> ExpertResponse:
        # πŸ‘‡ Use `responses.parse`
        response = self.client.responses.parse(
            model=MODEL,
            # πŸ‘‡ Explain what you're willing to receive
            instructions="You're a Python expert. Answer the question as best you can. Give a brief explanation and provide example code if applicable.",
            input=question,
            # πŸ‘‡ Pass expected output model
            text_format=ExpertResponse,
        )

        # πŸ‘‡ Pass the parsed model
        return response.output_parsed


def main():
    expert = PythonExpert()

    question = 'How do I print "Hello, world!" in Python?'
    response = expert.ask_the_expert(question)

    print(f"[yellow]Question:[/yellow] {question}")
    print(f"[yellow]Explanation:[/yellow]\n\t {response.explanation}")
    print(f"[yellow]Example Code[/yellow]:\n\t{response.example_code}")


if __name__ == "__main__":
    main()

And it works as expected:

Output JSON Example
JSON Output Example

Without it, you’d have to include more instructions to force ChatGPT to reply in the expected format. I did it many times, it's not a pleasant experience.

πŸ’° Cheaper API alternative to OpenAI's ChatGPT

DeepSeek is significantly cheaper than ChatGPT (from my experience a bit slower though):

ChatGPT Model Pricing/1M Input Tokens DeepSeek Model Pricing/1M Input Tokens
gpt-4o $2.50 deepseek-chat $0.27
o3 $10.0 deepseek-reasoner $0.55

Still not cheap enough? Okay, you can get this even lower by using it during off-peak hours giving you up to 75% OFF:

DeepSeek Model Cached Regular Hours Pricing/1M Tokens Discount Off-Peak Hours/1M Tokens
deepseek-chat No $0.27 $0.135
deepseek-chat Yes $0.07 $0.035
deepseek-reasoner No $0.55 $0.135
deepseek-reasoner Yes $0.14 $0.035

🐳 Python DeepSeek API vs ChatGPT API

Alright, let’s use DeepSeek, but... Now I have to install yet another lib and modify my working code?

No. You don't need to install anything else but openai (as we already did).

You can create your DeepSeek API Key here.

Create DeepSeek API Key
Create DeepSeek API Key

You just can't use client.responses.create, you need client.chat.completions.create.

import typing as t
from openai import OpenAI
from rich import print

API_KEY: t.Final = "sk-[REDACTED]"
MODEL: t.Final = "deepseek-chat"


class PythonExpert:
    def __init__(self):
        self.client = OpenAI(
            api_key=API_KEY,
            # πŸ‘‡ The trick!
            base_url="https://api.deepseek.com/v1",
        )

    def ask_the_expert(self, question: str):
        response = self.client.chat.completions.create(
            model=MODEL,
            messages=[
                # πŸ‘‡ System message to set the context, analogous to `instructions`
                {
                    "role": "system",
                    "content": "You're a Python expert. Answer the question as best you can.",
                },
                # πŸ‘‡ Actual request
                {"role": "user", "content": question},
            ],
        )

        return response


def main():
    expert = PythonExpert()

    question = 'How do I print "Hello, world!" in Python?'
    response = expert.ask_the_expert(question)
    # πŸ‘‡ Not much user friendly
    answer = response.choices[0].message.content

    print(f"[yellow]Question:[/yellow] {question}")
    print(f"\tAnswer: {answer}")


if __name__ == "__main__":
    main()

I hope you enjoyed learning these tricks. If it was useful give me a follow on X to be notified when I post new Python tricks.

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket