Skip to main content

Free Python client for DuckDuckGo AI Chat (duck.ai). Sync, streaming, image generation, image edit, multimodal vision, web search. Auto-retry on challenge failures. No API key required.

Project description

p2d-duck

pip install p2d-duck

Free, no-API-key Python client for DuckDuckGo AI Chat (duck.ai). Import name: duck_ai.


Features
--------
  Single sync client built on httpx
  Auto-retry with exponential backoff on challenge failures
  Browser-faithful session warm-up — first call succeeds, not the third
  6 chat models + image generation (see model table below)
  Reasoning effort control  (fast / reasoning)
  Image generation  (text-to-image)
  Image edit        (caption + source image -> edited image)
  Image upload      (vision / multimodal)
  Web search        (opt-in per call, supported models only)
  Multi-turn history (off by default, opt-in)
  Built-in x-vqd-hash-1 JS challenge solver via mini-racer
  CLI: p2d-duck
  No account · No API key · No server · No fee

Install

pip install p2d-duck

Requires Python 3.10+.


Models

Alias            Model ID                                   Reasoning   Vision   Web Search
-----------      -----------------------------------------  ---------   ------   ----------
gpt5_mini        gpt-5.4-mini                               yes         yes      yes
gpt5_nano        gpt-5.4-nano                               yes         yes      yes
claude           claude-haiku-4-5                           yes         yes      yes
mistral          mistral-small-2603                         no          no       no
gpt_oss          tinfoil/gpt-oss-120b                       yes         no       no
image            image-generation                           no          yes      no

Pass any alias, full model ID, or ModelType enum member to DuckChat(model=...).

from duck_ai import (
    DuckChat,
    gpt5_mini, gpt5_nano,
    claude, mistral, gpt_oss, image_generation,
)

Quickstart

from duck_ai import DuckChat, gpt5_mini

with DuckChat(model=gpt5_mini) as duck:
    print(duck.ask("Explain quantum tunneling in one sentence."))

Streaming

from duck_ai import DuckChat

with DuckChat() as duck:
    for chunk in duck.stream("Write a 4-line haiku about ducks."):
        print(chunk, end="", flush=True)

Reasoning effort

from duck_ai import DuckChat, gpt5_mini, claude

with DuckChat(model=gpt5_mini, effort="fast") as duck:
    print(duck.ask("Quick: 2+2?"))

with DuckChat(model=claude, effort="reasoning") as duck:
    print(duck.ask("Solve: I speak without a mouth..."))
Model         fast token    reasoning token    Default
-----------   ----------    ---------------    -------
gpt5_mini     minimal       low                low
gpt5_nano     minimal       low                low
claude        none          low                low
gpt_oss       low           low                low
mistral       (none)        (none)             (none)

Pass effort="fast" or effort="reasoning". Non-reasoning models silently ignore it.


Multi-turn conversation

History is off by default. Each ask / stream call is a fresh single-turn request unless you opt in.

from duck_ai import DuckChat, claude

# Per-client: enable at construction
with DuckChat(model=claude, history=True) as duck:
    duck.ask("My name is Alice. Remember it.")
    print(duck.ask("What is my name?"))   # -> Alice
    duck.reset()                          # clear the buffered turns

# Toggle live
with DuckChat(model=claude) as duck:
    duck.enable_history()
    duck.ask("Pick a number between 1 and 10.")
    print(duck.ask("What number did you pick?"))
    duck.disable_history()                # also clears the buffer

# Per-call override
with DuckChat() as duck:
    duck.ask("hi", remember=True)         # buffer this turn
    duck.ask("ignore me", remember=False) # do not buffer

Web search

from duck_ai import DuckChat, gpt5_mini, model_supports_web_search

assert model_supports_web_search(gpt5_mini)

with DuckChat(model=gpt5_mini) as duck:
    print(duck.ask(
        "What did Apple announce at WWDC this year?",
        web_search=True,
    ))

Supported on: gpt5_mini, gpt5_nano, claude. Silently ignored for other models.


Image generation

from duck_ai import DuckChat, image_generation

with DuckChat(model=image_generation) as duck:
    duck.generate_image(
        "a cute rubber duck wearing a wizard hat, digital art",
        save_to="duck_wizard.jpg",
    )

Image edit

from duck_ai import DuckChat, image_generation

with DuckChat(model=image_generation) as duck:
    duck.edit_image(
        "make the duck wear a tiny chef hat",
        "duck_wizard.jpg",
        save_to="duck_chef.jpg",
    )

Image upload (multimodal vision)

from duck_ai import DuckChat, ImagePart

with DuckChat() as duck:
    print(duck.ask_with_image("What is in this image?", "photo.jpg"))

    print(duck.ask([
        "Compare these two images:",
        ImagePart.from_path("a.png"),
        ImagePart.from_path("b.png"),
    ]))

If the selected model has no vision capability, the request is automatically routed to a vision-capable model (gpt-5.4-mini).


CLI

p2d-duck                                         interactive REPL
p2d-duck chat "Hello, who are you?"
p2d-duck -m claude chat "Hi Claude!"
p2d-duck -m gpt5_mini -e reasoning chat "Solve x^2 - 5x + 6 = 0"
p2d-duck chat "Describe this" --image cat.jpg
p2d-duck -m gpt5_mini chat "Top news today" --web-search
p2d-duck image "a watercolor moon over a lake" -o moon.jpg
p2d-duck edit "make the cat wear sunglasses" --image cat.jpg -o cat_cool.jpg
p2d-duck models

REPL commands: /reset, /history on|off, /quit


Reliability

1. Session warm-up     — visits duck.ai homepage on construction so cookies are
                         present before the first chat request.
2. Challenge rotation  — captures x-vqd-hash-1 from each response and reuses it
                         for the next call, matching the browser client behaviour.
3. Retry loop          — on ChallengeError, RemoteProtocolError, RateLimitError,
                         dropped streams, or an empty SSE response: re-fetches the
                         challenge and retries with exponential backoff + jitter.
4. Terminal errors     — ConversationLimitError is never retried.
5. Real RSA key        — uses cryptography to generate a real RSA-OAEP-256 JWK for
                         durable streams; refuses to fall back to a fake key.
duck = DuckChat(max_retries=4, backoff_base=0.6)

Exceptions

Exception               When
--------------------    -----------------------------------------------
DuckChatError           Generic error — base class for all below.
ChallengeError          Could not solve the x-vqd-hash-1 JS challenge.
RateLimitError          HTTP 429 from the server.
ConversationLimitError  Too many turns in one session (terminal).
APIError                Any other non-200 response (.status_code, .body).

How it works

DuckDuckGo's AI Chat backend (duck.ai/duckchat/v1/*) requires a per-request proof-of-work challenge encoded in the x-vqd-hash-1 header. The server returns an obfuscated JavaScript snippet that must be evaluated in a browser-like environment.

1. stubs.js     — minimal browser-DOM shim injected before the challenge script.
2. mini-racer   — embedded V8 isolate (no external Node.js install required).
3. SHA-256      — hashes the fingerprint values produced by the challenge.
4. RSA-OAEP-256 — real public key for durable (resumable) streams.

License

MIT. See LICENSE.


Disclaimer

Unofficial reverse-engineered client. Not affiliated with or endorsed by DuckDuckGo. Use at your own risk and respect duck.ai's terms of service. The backend may change at any time.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

p2d_duck-1.3.0.tar.gz (20.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

p2d_duck-1.3.0-py3-none-any.whl (21.1 kB view details)

Uploaded Python 3

File details

Details for the file p2d_duck-1.3.0.tar.gz.

File metadata

  • Download URL: p2d_duck-1.3.0.tar.gz
  • Upload date:
  • Size: 20.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for p2d_duck-1.3.0.tar.gz
Algorithm Hash digest
SHA256 5e79ed4ea49b625701638c0dcf7f271be381df925ad8c3c356834c14e425cba8
MD5 e35b39bdc417918a6650b62a37e27845
BLAKE2b-256 5f94b398cd3c6dca057ec34d12a11bc8d48629ea4b3b00ad921ca6aaa5068e79

See more details on using hashes here.

Provenance

The following attestation bundles were made for p2d_duck-1.3.0.tar.gz:

Publisher: publish.yml on pooraddyy/p2d-duck

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file p2d_duck-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: p2d_duck-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 21.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for p2d_duck-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 91d5d988c6b321916b60bffd220544ad5b5be1ba58a8cf4a0e056e719f3cb821
MD5 f8d7cfe434e192d3c84871526f0b65e6
BLAKE2b-256 4cda1f4a2f4de93cb58ec3eefff40d0f212ab3c0ef88be566543e3ddadfa3f93

See more details on using hashes here.

Provenance

The following attestation bundles were made for p2d_duck-1.3.0-py3-none-any.whl:

Publisher: publish.yml on pooraddyy/p2d-duck

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page