Skip to main content

Unofficial Qwen API client with OpenAI-compatible chat, streaming, web search, reasoning, and LlamaIndex support

Project description

qwen-chat

PyPI version License: MIT Python Version

An unofficial, feature-rich Python SDK and client wrapper for the Qwen AI Web API. It supports native Qwen chat, OpenAI-compatible Python usage, an OpenAI-compatible /v1 local server, streaming, multi-turn conversations, system prompts, web search, thinking/reasoning controls, automatic local image uploads, and LlamaIndex integration.


✨ Features

  • 🧠 Complete Model Support
    Interact with Qwen's latest web models including qwen3.6-plus, qwen-max-latest, qwq-32b, qwen2.5-omni-7b, and specialized vision/coder versions.

  • OpenAI-Compatible API
    Use client.chat.completions.create(...), client.responses.create(...), or run a local /v1 server for the official openai Python SDK.

  • 💬 Multi-turn Chat Sessions
    Have back-and-forth conversations that maintain context across messages — just like chatting in the Qwen web UI. Build interactive terminal chatbots or conversational agents easily.

  • ⚡ Synchronous & Asynchronous Clients
    Whether you're building a script or a highly concurrent async web server, qwen-chat has you covered with native create() and acreate() workflows.

  • 🌊 Real-time SSE Streaming (On/Off)
    Toggle stream=True or stream=False per request. Stream token-by-token responses directly to your UI or console, or get the full response at once.

  • 🎭 System & Assistant Roles
    Use system role messages to define the AI's personality, behavior, and instructions. Chain assistant and user roles for few-shot prompting and context injection.

  • 📸 Automatic Local Image Uploads
    Pass a local file path or raw bytes to an ImageBlock. The client automatically fetches STS tokens and uploads the image to Alibaba Cloud OSS under the hood — no boilerplate required!

  • 🔍 Integrated Web Search
    Toggle real-time search on or off per message to query live web data and receive detailed citations alongside responses.

  • 💡 Thinking & Reasoning Controls
    Adjust thinking budget settings to enable advanced reasoning capabilities on complex tasks.

  • 🦙 LlamaIndex Adapter
    Use Qwen directly inside LlamaIndex with QwenLlamaIndex from the same qwen_chat package. No separate qwen-api or qwen-llamaindex package is required.


📦 Installation

Install qwen-chat via pip:

pip install qwen-chat

⚙️ Environment Setup

To authenticate requests, extract your Authorization bearer token from the Qwen Web UI:

  1. Go to https://chat.qwen.ai and log in.
  2. Open developer tools (F12 or Ctrl+Shift+I / Cmd+Option+I) and navigate to the Network tab.
  3. Send any message in the chat interface.
  4. Locate the completions request (filter by Fetch/XHR).
  5. Click on the request and go to the Headers tab. Copy the value of the Authorization header without the word "Bearer " (just copy the token starting with eyJ...).
  6. Save this value in a .env file in the root of your project:
QWEN_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Note: You do not need to set up cookies. The package handles cookies automatically.


🚀 Usage Examples

OpenAI-Compatible Python API

You can use OpenAI-style methods directly from qwen-chat:

from qwen_chat import OpenAI

client = OpenAI()

response = client.chat.completions.create(
    model="qwen3.6-plus",
    messages=[
        {"role": "system", "content": "You are a concise assistant."},
        {"role": "user", "content": "What is Python?"},
    ],
)

print(response.choices[0].message.content)

Streaming:

from qwen_chat import OpenAI

client = OpenAI()

stream = client.chat.completions.create(
    model="qwen3.6-plus",
    messages=[{"role": "user", "content": "Write one short paragraph about Python."}],
    stream=True,
)

for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="", flush=True)
print()

Qwen-specific options are available in the OpenAI-style API:

from qwen_chat import OpenAI

client = OpenAI()

response = client.chat.completions.create(
    model="qwen3.6-plus",
    messages=[{"role": "user", "content": "Solve this step by step: 120km in 2h, stop 30m, then 90km in 1.5h. Average speed?"}],
    thinking=True,
    auto_thinking=True,
    thinking_mode="Auto",
    thinking_format="summary",
    research_mode="normal",
    thinking_budget=4096,
)

print(response.choices[0].message.content)

Web search:

from qwen_chat import OpenAI

client = OpenAI()

stream = client.chat.completions.create(
    model="qwen3.6-plus",
    messages=[{"role": "user", "content": "What are the latest developments in AI?"}],
    stream=True,
    web_search=True,
    auto_search=True,
)

for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="", flush=True)
print()

Responses API style:

from qwen_chat import OpenAI

client = OpenAI()

response = client.responses.create(
    model="qwen3.6-plus",
    instructions="You are a concise assistant.",
    input="Reply with only: ok",
)

print(response.output_text)

OpenAI-Compatible Local Server

Run a server that works with the official openai Python package:

qwen-chat-openai --host 0.0.0.0 --port 8000

By default, the command clears the terminal, prints a colored Qwen Chat startup banner, and binds to 0.0.0.0 so it can be reached from outside the machine when your firewall allows it.

For local-only usage:

qwen-chat-openai --host 127.0.0.1 --port 8000

For a quiet startup without clearing the terminal or printing the banner:

qwen-chat-openai --host 0.0.0.0 --port 8000 --no-clear --no-banner

Then call it with the official OpenAI SDK:

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="your-qwen-auth-token",
)

response = client.chat.completions.create(
    model="qwen3.6-plus",
    messages=[{"role": "user", "content": "Reply with only: ok"}],
)

print(response.choices[0].message.content)

The server exposes:

  • GET /v1/models
  • POST /v1/chat/completions
  • POST /v1/responses
  • GET /health

The api_key passed to the official OpenAI SDK is used as the Qwen auth token. If no SDK key is passed, the server falls back to QWEN_AUTH_TOKEN from .env.

Run 24/7 on a VPS

Use systemd for a stable background service.

Create an environment file:

sudo mkdir -p /etc/qwen-chat
sudo nano /etc/qwen-chat/openai.env

Add your token:

QWEN_AUTH_TOKEN=your-qwen-auth-token
QWEN_OPENAI_HOST=0.0.0.0
QWEN_OPENAI_PORT=8000

Create a systemd service:

sudo nano /etc/systemd/system/qwen-chat-openai.service

Paste:

[Unit]
Description=qwen-chat OpenAI-compatible server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
EnvironmentFile=/etc/qwen-chat/openai.env
ExecStart=/usr/local/bin/qwen-chat-openai --host ${QWEN_OPENAI_HOST} --port ${QWEN_OPENAI_PORT} --no-clear
Restart=always
RestartSec=5
User=root

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable qwen-chat-openai
sudo systemctl start qwen-chat-openai

Check status and logs:

sudo systemctl status qwen-chat-openai
sudo journalctl -u qwen-chat-openai -f

Allow the port if your VPS firewall is enabled:

sudo ufw allow 8000/tcp

Qwen-specific options can be passed through extra_body:

response = client.chat.completions.create(
    model="qwen3.6-plus",
    messages=[{"role": "user", "content": "Solve this step by step."}],
    extra_body={
        "thinking": True,
        "auto_thinking": True,
        "thinking_mode": "Auto",
        "thinking_format": "summary",
        "research_mode": "normal",
        "thinking_budget": 4096,
    },
)

Web search through the OpenAI SDK:

stream = client.chat.completions.create(
    model="qwen3.6-plus",
    messages=[{"role": "user", "content": "Latest AI news?"}],
    stream=True,
    extra_body={"web_search": True, "auto_search": True},
)

for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="", flush=True)
print()

1. Basic Text Completion

The simplest way to get a response from Qwen:

from qwen_chat import Qwen
from qwen_chat.core.types.chat import ChatMessage

client = Qwen()

messages = [
    ChatMessage(role="user", content="What is Python?")
]

response = client.chat.create(messages=messages, model="qwen3.6-plus")
print("🤖", response.choices.message.content)

LlamaIndex Usage

Use Qwen as a LlamaIndex LLM from the same qwen-chat package:

from qwen_chat import QwenLlamaIndex

llm = QwenLlamaIndex(model="qwen3.6-plus")

response = llm.complete("Reply with only: ok")
print(response.text)

Chat example:

from llama_index.core.base.llms.types import ChatMessage
from qwen_chat import QwenLlamaIndex

llm = QwenLlamaIndex(model="qwen3.6-plus")

response = llm.chat([
    ChatMessage(role="user", content="What is LlamaIndex?")
])

print(response.message.content)

Streaming example:

from qwen_chat import QwenLlamaIndex

llm = QwenLlamaIndex(model="qwen3.6-plus")

for chunk in llm.stream_complete("Write one sentence about Python."):
    print(chunk.delta, end="", flush=True)

2. Streaming On / Off

Stream OFF (get full response at once):

from qwen_chat import Qwen
from qwen_chat.core.types.chat import ChatMessage

client = Qwen()
messages = [ChatMessage(role="user", content="Tell me a short joke.")]

# stream=False (default) — returns complete response
response = client.chat.create(messages=messages, model="qwen3.6-plus", stream=False)
print("🤖", response.choices.message.content)

Stream ON (token-by-token output):

from qwen_chat import Qwen
from qwen_chat.core.types.chat import ChatMessage

client = Qwen()
messages = [ChatMessage(role="user", content="Write a poem about the ocean.")]

# stream=True — yields chunks in real-time
stream = client.chat.create(messages=messages, model="qwen3.6-plus", stream=True)

print("🤖 ", end="")
for chunk in stream:
    print(chunk.choices[0].delta.content, end="", flush=True)
print()

3. System Role & Assistant Role (Custom Personality)

Use the system role to define how the AI behaves. Use assistant role for few-shot examples:

from qwen_chat import Qwen
from qwen_chat.core.types.chat import ChatMessage

client = Qwen()

messages = [
    # System prompt — defines AI personality and behavior
    ChatMessage(
        role="system",
        content="You are a friendly pirate captain. Always respond in pirate speak with lots of 'Arrr!' and nautical references."
    ),
    # User message
    ChatMessage(
        role="user",
        content="What's the weather like today?"
    )
]

response = client.chat.create(messages=messages, model="qwen3.6-plus")
print("🏴‍☠️", response.choices.message.content)

Few-shot prompting with assistant role:

from qwen_chat import Qwen
from qwen_chat.core.types.chat import ChatMessage

client = Qwen()

messages = [
    ChatMessage(role="system", content="You are a helpful translator. Translate English to Bengali."),
    # Few-shot example
    ChatMessage(role="user", content="Hello"),
    ChatMessage(role="assistant", content="হ্যালো"),
    ChatMessage(role="user", content="How are you?"),
    ChatMessage(role="assistant", content="আপনি কেমন আছেন?"),
    # Actual request
    ChatMessage(role="user", content="I love programming")
]

response = client.chat.create(messages=messages, model="qwen3.6-plus")
print("🤖", response.choices.message.content)

4. Interactive Terminal Chat (Multi-turn Conversation)

Build a fully interactive terminal chatbot that maintains conversation context across multiple turns — just like the Qwen web UI.

How it works: The Qwen API remembers your conversation server-side via chat_id and parent_id. You only need to send the new message each turn — the AI already knows the full conversation history.

from qwen_chat import Qwen
from qwen_chat.core.types.chat import ChatMessage

def main():
    client = Qwen()
    print("💬 Qwen Chat Terminal")
    print("Type 'exit' to quit\n")

    # Optional: send a system prompt as the very first message
    system_msg = [
        ChatMessage(
            role="system",
            content="You are a helpful and friendly AI assistant."
        )
    ]
    client.chat.create(messages=system_msg, model="qwen3.6-plus")

    while True:
        try:
            user_input = input("🧑 You: ").strip()
        except (EOFError, KeyboardInterrupt):
            print("\nBye! 👋")
            break

        if not user_input or user_input.lower() == "exit":
            print("Bye! 👋")
            break

        # Send only the new user message — the API remembers the conversation
        messages = [ChatMessage(role="user", content=user_input)]

        response = client.chat.create(
            messages=messages,
            model="qwen3.6-plus",
            temperature=0.7
        )

        print(f"🤖 AI: {response.choices.message.content}\n")

if __name__ == "__main__":
    main()

Interactive chat with streaming output:

from qwen_chat import Qwen
from qwen_chat.core.types.chat import ChatMessage

def main():
    client = Qwen()
    print("💬 Qwen Streaming Chat")
    print("Type 'exit' to quit\n")

    while True:
        try:
            user_input = input("🧑 You: ").strip()
        except (EOFError, KeyboardInterrupt):
            print("\nBye! 👋")
            break

        if not user_input or user_input.lower() == "exit":
            print("Bye! 👋")
            break

        # Send only the new message each turn
        messages = [ChatMessage(role="user", content=user_input)]

        stream = client.chat.create(
            messages=messages,
            model="qwen3.6-plus",
            stream=True
        )

        print("🤖 AI: ", end="")
        for chunk in stream:
            text = chunk.choices[0].delta.content
            print(text, end="", flush=True)
        print("\n")

if __name__ == "__main__":
    main()

5. Web Search with Citations

from qwen_chat import Qwen
from qwen_chat.core.types.chat import ChatMessage

client = Qwen()

messages = [
    ChatMessage(
        role="user",
        content="What are the latest developments in AI?",
        web_search=True
    )
]

stream = client.chat.create(messages=messages, model="qwen3.6-plus", stream=True)

for chunk in stream:
    delta = chunk.choices[0].delta
    if delta.extra and delta.extra.web_search_info:
        print("\n🔍 Sources:")
        for source in delta.extra.web_search_info:
            print(f"  • {source.title}: {source.url}")
        print()
    print(delta.content, end="", flush=True)
print()

6. Automatic Local Image Upload (Vision)

Just pass your local image path directly inside ImageBlock. The client handles the secure upload automatically:

from qwen_chat import Qwen
from qwen_chat.core.types.chat import ChatMessage, TextBlock, ImageBlock

client = Qwen()

messages = [
    ChatMessage(
        role="user",
        blocks=[
            TextBlock(text="What's in this image? Describe it in detail."),
            ImageBlock(path="photo.jpg")  # Auto-uploaded to Alibaba OSS!
        ]
    )
]

response = client.chat.create(messages=messages, model="qwen3.6-plus")
print("🤖", response.choices.message.content)

7. Async Usage

import asyncio
from qwen_chat import Qwen
from qwen_chat.core.types.chat import ChatMessage

async def main():
    client = Qwen()
    messages = [ChatMessage(role="user", content="Explain quantum computing in one sentence.")]
    
    response = await client.chat.acreate(messages=messages, model="qwen3.6-plus")
    print("🤖", response.choices.message.content)

asyncio.run(main())

8. Thinking & Reasoning Mode

Enable deep thinking for complex tasks:

from qwen_chat import Qwen
from qwen_chat.core.types.chat import ChatMessage

client = Qwen()

messages = [
    ChatMessage(
        role="user",
        content="Solve this step by step: If a train travels 120km in 2 hours, then stops for 30 minutes, then travels 90km in 1.5 hours, what is the average speed for the entire journey?",
        thinking=True,
        thinking_budget=4096
    )
]

response = client.chat.create(messages=messages, model="qwen3.6-plus")
print("🤖", response.choices.message.content)

🙋‍♂️ Contributing

Contributions are welcome! Here's how:

  1. Fork the project
  2. Create your feature branch (git checkout -b feature/awesome-feature)
  3. Commit your changes (git commit -m 'Add awesome feature')
  4. Push to the branch (git push origin feature/awesome-feature)
  5. Open a Pull Request

📃 License

This project is licensed under the MIT License.


📞 Contact & Support

Telegram

For queries, support, or custom integrations, feel free to reach out!


Made with ❤️ by Shahadat Hassan

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

qwen_chat-1.0.8.tar.gz (46.2 kB view details)

Uploaded Source

Built Distribution

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

qwen_chat-1.0.8-py3-none-any.whl (46.6 kB view details)

Uploaded Python 3

File details

Details for the file qwen_chat-1.0.8.tar.gz.

File metadata

  • Download URL: qwen_chat-1.0.8.tar.gz
  • Upload date:
  • Size: 46.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for qwen_chat-1.0.8.tar.gz
Algorithm Hash digest
SHA256 c470f19b7c8eb8b7c0d205d473504da6e6f6e40a9187c4aa37a8cef966fe6e3a
MD5 2a1f46dae90f2de31c9cfb5e647afc3f
BLAKE2b-256 88e2f195c5ff50274f28cdabf6cb1feaf85f96ea94f0a45e18254a0690dd9aa9

See more details on using hashes here.

File details

Details for the file qwen_chat-1.0.8-py3-none-any.whl.

File metadata

  • Download URL: qwen_chat-1.0.8-py3-none-any.whl
  • Upload date:
  • Size: 46.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.7

File hashes

Hashes for qwen_chat-1.0.8-py3-none-any.whl
Algorithm Hash digest
SHA256 ae532c709f7fcd3837918368e0ab707b815952c8aecaa20fde0e54a6bd15411f
MD5 924e9ea18e625e3b952bec734ea0c8a9
BLAKE2b-256 80f6f312a8de99c6c6fb9bbc70c7b590e5c6508ee302f6a5a3e8d467502e20fe

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 Pingdom Monitoring Sentry Error logging StatusPage Status page