Skip to main content

Qwen-Reverse

Python PyPI version License: MIT

qwen-reverse logo    live streaming demo

Reverse-engineered async Python client for chat.qwen.ai — text chat, streaming with real-time reasoning, tool calling, image and video generation. No official API key required (works anonymously).

pip install qwen-reverse

Features

  • Chat — one-shot, streaming (SSE incremental), multi-turn with conversation memory (conversation_id / parent_id chaining)
  • Real-time reasoningreasoning events streamed token-by-token before the answer, in both plain and multi-turn mode
  • Tool calling — OpenAI-style function definitions; either let the SDK execute them (JSON-stringified follow-up) or handle them yourself with emit_tool_calls=True
  • Vision — image upload + chat about it (pass files/URLs/bytes)
  • Image editing (i2i)chat_type="image_edit": edit an uploaded image (add/remove/modify) and get back CDN URLs
  • File uploadupload() uploads local files (or URLs/bytes) into the web API for vision/editing chats
  • Image / video generation — t2i and t2v returning CDN URLs (cdn.qwenlm.ai)
  • No account required — the web API works without a token; OAuth device-flow login (chat.qwen.ai account) is also implemented
  • Optional FastAPI server — OpenAI-compatible /v1/chat/completions and /v1/models (see server/)

Quickstart

import asyncio
from qwen_reverse import Generative

async def main():
    gen = Generative()
    print(await gen.generate("Explain what a kernel is in 2 lines"))

asyncio.run(main())

Streaming with real-time thinking

import asyncio
from qwen_reverse import Generative

async def main():
    gen = Generative()
    async for event in gen.stream("Explain in 2 sentences what an OS kernel is"):
        if event["type"] == "reasoning":
            print(f"\r\033[90m{event['data']}\033[0m", end="", flush=True)
        elif event["type"] == "content":
            print(event["data"], end="", flush=True)

asyncio.run(main())

Multi-turn conversation

import asyncio
from qwen_reverse import Conversation

async def main():
    conv = Conversation()
    r1 = await conv.send("My name is Popbob and I work with kernels in C.")
    r2 = await conv.send("What is my name?")  # remembers turn 1
    print(r2)  # "Popbob"
    print(conv.conversation_id, conv.parent_id)

asyncio.run(main())

Tool calling (automatic execution)

import asyncio
from qwen_reverse import Conversation

WEATHER_TOOL = {
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}

async def main():
    conv = Conversation(tools=[WEATHER_TOOL])
    async for event in conv.stream("What's the weather in Buenos Aires?"):
        if event["type"] == "tool_calls":
            print("[tool_calls]", event["data"])
        elif event["type"] == "content":
            print(event["data"], end="", flush=True)

asyncio.run(main())

For raw tool-call JSON instead of execution, pass emit_tool_calls=True to stream()/create_chat().

Image generation

import asyncio
from qwen_reverse import Image

async def main():
    img = Image()
    urls = await img.generate(
        "A cyberpunk dragon flying over a neon city, anime style",
        aspect_ratio="16:9",
    )
    print(urls[0])  # https://cdn.qwenlm.ai/output/...

asyncio.run(main())

Upload a file + chat with vision

import asyncio
from qwen_reverse import Conversation

async def main():
    conv = Conversation(model="qwen3-vl-plus")  # a vision-capable model
    reply = await conv.send(
        "What is written on the whiteboard?",
        files=["photo.jpg"],  # path | bytes | URL | already-uploaded dict
    )
    print(reply)

asyncio.run(main())

files accepts local paths, raw bytes, http(s) URLs (downloaded then uploaded) or a pre-uploaded file dict. Use upload() to upload manually:

import asyncio
from qwen_reverse import upload

async def main():
    file_dict = await upload("diagram.png")        # -> {"id", "url", "type", ...}
    # reuse file_dict in multiple chats without re-uploading
asyncio.run(main())

Edit an image (image-to-image)

import asyncio
from qwen_reverse import ImageEdit

async def main():
    img = ImageEdit()  # chat_type="image_edit"
    urls = await img.edit(
        "Add a bold arrow crossing through the logo from top-left to bottom-right, "
        "keep the original violet symbol exactly the same",
        "logo.png",                # path | bytes | URL | uploaded dict
        aspect_ratio="1:1",
    )
    print(urls[0])  # edited image CDN URL

asyncio.run(main())

You can also call Generative(...).edit_image(prompt, image, aspect_ratio=...) directly.

API

Symbol Description
Generative(model=..., token=...) .generate(), .stream() (events: reasoning, content, usage, tool_calls, done)
Conversation(token=..., tools=...) .send(), .stream(), .reset() — persists conversation_id/parent_id across turns
Image(model=...), Video(model=...), ImageEdit(model=...) .generate(prompt, aspect_ratio=...) → list of CDN URLs; ImageEdit.edit(prompt, image, aspect_ratio=...) for image-to-image editing
create_chat(model, messages, ...) low-level async generator; conversation_id/parent_id for multi-turn
fetch_models() available models (qwen3.8-max, qwen3.7-plus, qwen3-vl-plus, ...)
upload(data, filename=...) upload a local path/bytes (or fetch+upload a URL) → file payload dict; returns the web-API file object
resolve_files(files) normalize a list of paths/bytes/URLs/dicts into upload-ready payload dicts
upload_file(...) low-level file upload (advanced: pass your own session/headers)
start_device_login() / complete_device_login(...) OAuth device flow
QwenOAuth2Client, SharedTokenManager token management
generate_cookies() / generate_fingerprint() / BXUAGenerator anti-bot primitives (cookies ssxmod_itna, bx-umidtoken, fingerprint)

Events emitted by stream()

Event Data When
reasoning str (chunk) during phase: "think" — the model's chain of thought
content str (chunk) during phase: "answer" — the actual reply
tool_calls list[dict] when the model requests functions
image / image_done {"url", "extra"} / None image generation progress
usage dict token usage (input_tokens, output_tokens, ...)
done final event; carries conversation_id and parent_id

Authentication

Anonymous mode works — no token required for most features (lower rate limits). For higher limits, log in with a chat.qwen.ai account:

import asyncio
from qwen_reverse import start_device_login, complete_device_login

async def main():
    client, data = await start_device_login()
    print(data["verification_uri_complete"])  # open in a logged-in browser
    # after authorizing:
    token = await complete_device_login(client, data)
    print(token)

Models

Defaults to qwen3.8-max. Others: qwen3.7-plus, qwen3.7-max, plus vision / image / video models — see qwen_reverse/models.py and fetch_models().

Running the tests

pip install -e ".[dev]"
pytest

Optional server (OpenAI-compatible)

pip install "qwen-reverse[server]"
cd server
uvicorn app.main:app --port 8000
# GET  /v1/models
# POST /v1/chat/completions  (OpenAI-style, streaming SSE)

How it works

The client replicates what the official web app does against chat.qwen.ai:

  1. Create a chat via POST /api/v2/chats/new (gets a chat_id)
  2. Stream the response via POST /api/v2/chat/completions?chat_id=... with version: 2.1, incremental_output and a feature_config that enables the reasoning stream
  3. Multi-turn chaining uses the server's response_id (assistant message fid) as the next turn's parent_id
  4. Anti-bot headers are regenerated per request: ssxmod_itna cookies (custom LZW + custom base64), bx-umidtoken, fingerprint

Disclaimer

This project is for educational and research purposes. It is not affiliated with or endorsed by Alibaba/Qwen. Use at your own risk — the endpoints may change or the service may rate-limit or block unofficial clients. MIT licensed; reverse-engineering references based on g4f (MIT).

Download files

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

Source Distribution

qwen_reverse-0.1.3.tar.gz (31.5 kB view details)

Uploaded Source

Built Distribution

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

qwen_reverse-0.1.3-py3-none-any.whl (26.7 kB view details)

Uploaded Python 3

File details

Details for the file qwen_reverse-0.1.3.tar.gz.

File metadata

  • Download URL: qwen_reverse-0.1.3.tar.gz
  • Upload date:
  • Size: 31.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.3

File hashes

Hashes for qwen_reverse-0.1.3.tar.gz
Algorithm Hash digest
SHA256 4d61a19744b819ec81b64e129e48397cbe02812c024369e6a183ee23e4241612
MD5 3159c3d4c497c276933ed59622cc5ab9
BLAKE2b-256 560671191b6ddf79e06f8d1db0b9e48ea9495869694f8d336de502fac1ae5385

See more details on using hashes here.

File details

Details for the file qwen_reverse-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: qwen_reverse-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 26.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.3

File hashes

Hashes for qwen_reverse-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 37343c58272556a37f526659e1698f3eb0e0975b433d62e48a3bea653d78585f
MD5 da12c93f1561e9eb6786d04908c6dca8
BLAKE2b-256 38f006e1995e3aba127bdbcaeacb0231df79a2564f9541a429a63f8243cb1a28

See more details on using hashes here.

Supported by

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