Unofficial Python client for DeepSeek — supports deepseek-v4-flash & deepseek-v4-pro models, thinking, web search, and file/image uploads.
Project description
Unofficial Python client for DeepSeek — no API subscription required.
Just your auth token from chat.deepseek.com.
Features
Chat deepseek-v4-flash and deepseek-v4-pro
Vision Image and document uploads (JPEG, PNG, PDF, DOCX, CSV, ...)
Thinking Chain-of-thought reasoning
Web Search Live search grounding
Streaming Token-by-token output
Multi-turn Session memory with automatic parent tracking
Installation
pip install p2d-deepseek
pip install p2d-deepseek --upgrade
Package name on PyPI is
p2d-deepseek. Import name isdeepseek.
Getting Your Auth Token
Method 1 — LocalStorage (Desktop)
1. Open https://chat.deepseek.com and log in
2. Press F12 -> Application tab -> Local Storage
3. Click https://chat.deepseek.com
4. Find key userToken and copy the value
Method 2 — Network Tab (Desktop)
1. Open https://chat.deepseek.com and log in
2. Press F12 -> Network tab
3. Send any message
4. Click any request to chat.deepseek.com
5. Copy the authorization header value (without "Bearer ")
Method 3 — Kiwi Browser (Android)
1. Install Kiwi Browser from Play Store
2. Open https://chat.deepseek.com and log in
3. Menu -> Developer Tools -> Application -> Local Storage
4. Copy the userToken value
Tokens expire when your DeepSeek session ends. Refresh using any method above.
Quick Start
from deepseek import DeepSeekClient
client = DeepSeekClient(api_key="YOUR_TOKEN_HERE")
response = client.chat("What is the capital of France?")
print(response.response)
# Paris
Models
| Model | API name | Description |
|---|---|---|
deepseek-v4-flash |
default |
Fast, general purpose — default |
deepseek-v4-pro |
expert |
Deeper reasoning, more detailed answers |
response = client.chat("Solve this integral", model="deepseek-v4-pro")
Chat Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
prompt |
str |
required | Your message |
model |
str |
deepseek-v4-flash |
Model to use |
thinking |
bool |
False |
Enable chain-of-thought reasoning |
search |
bool |
False |
Enable live web search |
session_id |
str |
None |
Reuse an existing chat session |
parent_message_id |
int |
None |
Override parent turn (auto-tracked by default) |
files |
list[str] |
None |
Local file paths to upload and attach |
file_ids |
list[str] |
None |
Already-uploaded file IDs to reuse |
Response Fields
response = client.chat("Your prompt here", thinking=True)
response.response # Final clean answer
response.thinking_content # Internal reasoning (only when thinking=True)
response.full_response # Thinking + answer combined — use this in bots
response.session_id # Continue the conversation
response.message_id # Message ID in the session
response.status # Response status from DeepSeek
Thinking Mode
response = client.chat("What is 17 * 23?", thinking=True)
print(response.response)
# 391
print(response.thinking_content)
# We need to compute 17 × 23. 17 × 20 = 340, 17 × 3 = 51, total = 391.
print(response.full_response)
# We need to compute 17 × 23. 17 × 20 = 340, 17 × 3 = 51, total = 391.
#
# 17 multiplied by 23 equals 391.
In bots, always use
response.full_response— it works correctly whether thinking is on or off.
Image and Document Uploads
Attach directly in chat()
# Single image
response = client.chat(
"What is in this image?",
files=["photo.jpg"],
)
# Multiple files
response = client.chat(
"Compare these two screenshots",
files=["before.png", "after.png"],
)
# PDF with thinking
response = client.chat(
"What are the three main risks in this document?",
files=["annual_report.pdf"],
thinking=True,
)
Upload once, reuse across multiple turns
file_id = client.upload_file("report.pdf")
r1 = client.chat("Summarise this in 3 bullets", file_ids=[file_id])
r2 = client.chat("What conclusion does the author reach?", file_ids=[file_id], session_id=r1.session_id)
r3 = client.chat("List every cited source", file_ids=[file_id], session_id=r1.session_id)
Mix new uploads with existing IDs
existing_id = client.upload_file("contract.pdf")
response = client.chat(
"Are these from the same contract?",
files=["page_scan.jpg"],
file_ids=[existing_id],
)
Inspect file metadata
infos = client.fetch_files([file_id])
print(infos[0])
# {
# "id": "file-...",
# "file_name": "report.pdf",
# "status": "SUCCESS",
# "file_size": 234567,
# "is_image": False,
# "token_usage": 812,
# }
upload_file() parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
file_path |
str |
required | Local path to the file |
thinking |
bool |
False |
Flag for thinking-mode usage |
poll_interval |
float |
1.0 |
Seconds between status polls |
timeout |
float |
120.0 |
Max seconds before timing out |
Returns: file_id string once status is SUCCESS.
Supported file types
| Category | Extensions |
|---|---|
| Images | .jpg .jpeg .png .gif .webp .bmp .heic .tiff |
| Documents | .pdf |
| Office | .doc .docx .xls .xlsx .ppt .pptx |
| Text | .txt .md .rtf |
| Code | .py .js .ts .tsx .java .kt .swift .c .cpp .cs .go .rs .rb .php .sh .lua .r .scala .dart |
| Web | .html .css .scss .vue .svelte |
| Data | .json .xml .yaml .toml .csv .tsv .sql .ipynb .log |
Streaming
from deepseek import DeepSeekClient
client = DeepSeekClient(api_key="YOUR_TOKEN_HERE")
for chunk in client.chat_stream("Explain black holes in simple words"):
print(chunk, end="", flush=True)
print()
Streaming with pro model
for chunk in client.chat_stream("Prove that sqrt(2) is irrational", model="deepseek-v4-pro"):
print(chunk, end="", flush=True)
print()
chat_stream() parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
prompt |
str |
required | Your message |
model |
str |
deepseek-v4-flash |
Model to use |
thinking |
bool |
False |
Thinking mode (only final answer is yielded) |
search |
bool |
False |
Live web search |
session_id |
str |
None |
Reuse an existing session |
parent_message_id |
int |
None |
Override parent turn |
file_ids |
list[str] |
None |
Already-uploaded file IDs to attach |
Multi-turn Conversations
r1 = client.chat("My name is Alex")
r2 = client.chat("What is my name?", session_id=r1.session_id)
print(r2.response)
# Your name is Alex.
The client tracks the last message_id per session automatically — each turn appends correctly without needing parent_message_id.
Error Handling
from deepseek import DeepSeekClient, DeepSeekConnectionError, DeepSeekAPIError
client = DeepSeekClient(api_key="YOUR_TOKEN_HERE")
try:
response = client.chat("Hello!")
print(response.response)
except DeepSeekConnectionError as e:
print(f"Connection failed: {e}")
except DeepSeekAPIError as e:
print(f"API error: {e}")
| Exception | When it happens |
|---|---|
DeepSeekConnectionError |
Network error or cannot reach DeepSeek |
DeepSeekAPIError |
Token expired/invalid, file parse failure, or DeepSeek returned an error |
Full Examples
All features combined
from deepseek import DeepSeekClient
client = DeepSeekClient(api_key="YOUR_TOKEN_HERE")
# Upload once
file_id = client.upload_file("data.csv")
# Chat with file + thinking + search
r1 = client.chat(
"Summarise this dataset and find recent trends",
file_ids=[file_id],
model="deepseek-v4-pro",
thinking=True,
search=True,
)
print(r1.full_response)
# Continue the conversation
r2 = client.chat(
"Which column shows the most variance?",
file_ids=[file_id],
session_id=r1.session_id,
)
print(r2.response)
Telegram bot with full feature support
A production-ready bot supporting text, photos, documents, session memory, /think, /search, /model, /reset, /status, and long-message splitting.
pip install p2d-deepseek python-telegram-bot
import asyncio, os, tempfile
from collections import defaultdict
from telegram import Update
from telegram.constants import ChatAction
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, filters, ContextTypes
from deepseek import DeepSeekClient, DeepSeekConnectionError, DeepSeekAPIError
DEEPSEEK_TOKEN = "YOUR_DEEPSEEK_TOKEN"
BOT_TOKEN = "YOUR_TELEGRAM_BOT_TOKEN"
client = DeepSeekClient(api_key=DEEPSEEK_TOKEN)
user_state = defaultdict(lambda: {"session_id": None, "model": "deepseek-v4-flash", "thinking": False, "search": False})
album_buf = defaultdict(list)
def get(uid): return user_state[uid]
async def dl(f, suffix):
fd, p = tempfile.mkstemp(suffix=suffix); os.close(fd)
await f.download_to_drive(p); return p
async def send(msg, text):
for i in range(0, max(len(text), 1), 4000):
await msg.reply_text(text[i:i+4000] or "(empty)")
async def process(uid, msg, prompt, paths):
s = get(uid)
try:
await msg.chat.send_action(ChatAction.TYPING)
r = await asyncio.to_thread(
client.chat, prompt,
model=s["model"], thinking=s["thinking"], search=s["search"],
session_id=s["session_id"], files=paths or None
)
s["session_id"] = r.session_id
await send(msg, r.full_response)
except (DeepSeekConnectionError, DeepSeekAPIError) as e:
await msg.reply_text(str(e))
finally:
for p in paths:
try: os.remove(p)
except: pass
async def flush_album(ctx):
uid, gid = ctx.job.data
items = album_buf.pop(gid, [])
if not items: return
items.sort(key=lambda x: x["mid"])
paths = [await dl(it["f"], it["s"]) for it in items]
prompt = next((it["c"] for it in items if it["c"]), "Describe these")
await process(uid, items[0]["msg"], prompt, paths)
async def on_message(upd: Update, ctx: ContextTypes.DEFAULT_TYPE):
msg = upd.message
if not msg: return
uid = upd.effective_user.id
if msg.media_group_id:
f = await (msg.photo[-1] if msg.photo else msg.document).get_file()
s = ".jpg" if msg.photo else (os.path.splitext(msg.document.file_name or "")[1] or ".bin")
album_buf[msg.media_group_id].append({"mid": msg.message_id, "msg": msg, "f": f, "s": s, "c": msg.caption or ""})
for j in ctx.job_queue.get_jobs_by_name(f"a:{msg.media_group_id}"): j.schedule_removal()
ctx.job_queue.run_once(flush_album, 1.5, data=(uid, msg.media_group_id), name=f"a:{msg.media_group_id}")
return
paths, prompt = [], msg.text or msg.caption or "Describe this"
if msg.photo:
paths.append(await dl(await msg.photo[-1].get_file(), ".jpg"))
elif msg.document:
ext = os.path.splitext(msg.document.file_name or "")[1] or ".bin"
paths.append(await dl(await msg.document.get_file(), ext))
await process(uid, msg, prompt, paths)
async def cmd_think(u, c):
s = get(u.effective_user.id); arg = c.args[0] if c.args else None
s["thinking"] = (arg.lower() in {"on","1","true","yes"}) if arg else not s["thinking"]
await u.message.reply_text(f"Thinking: {'ON' if s['thinking'] else 'OFF'}")
async def cmd_search(u, c):
s = get(u.effective_user.id); arg = c.args[0] if c.args else None
s["search"] = (arg.lower() in {"on","1","true","yes"}) if arg else not s["search"]
await u.message.reply_text(f"Web search: {'ON' if s['search'] else 'OFF'}")
async def cmd_model(u, c):
s = get(u.effective_user.id)
valid = {"deepseek-v4-flash", "deepseek-v4-pro"}
if c.args and c.args[0] in valid:
s["model"] = c.args[0]; await u.message.reply_text(f"Model: {s['model']}")
else:
await u.message.reply_text("Usage: /model deepseek-v4-flash|deepseek-v4-pro")
async def cmd_reset(u, c):
get(u.effective_user.id)["session_id"] = None
await u.message.reply_text("Session cleared.")
async def cmd_status(u, c):
s = get(u.effective_user.id)
await u.message.reply_text(
f"Model: {s['model']}\nThinking: {'ON' if s['thinking'] else 'OFF'}\n"
f"Search: {'ON' if s['search'] else 'OFF'}\nSession: {'active' if s['session_id'] else 'new'}"
)
async def cmd_start(u, c):
await u.message.reply_text(
"DeepSeek bot — send text, photos or documents.\n\n"
"/think on|off toggle reasoning\n"
"/search on|off toggle web search\n"
"/model <name> switch model\n"
"/reset new conversation\n"
"/status current settings"
)
app = ApplicationBuilder().token(BOT_TOKEN).build()
for cmd, fn in [("start",cmd_start),("help",cmd_start),("reset",cmd_reset),
("think",cmd_think),("search",cmd_search),("model",cmd_model),("status",cmd_status)]:
app.add_handler(CommandHandler(cmd, fn))
app.add_handler(MessageHandler((filters.TEXT|filters.PHOTO|filters.Document.ALL)&~filters.COMMAND, on_message))
app.run_polling()
License
MIT — by addy
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file p2d_deepseek-0.2.4.tar.gz.
File metadata
- Download URL: p2d_deepseek-0.2.4.tar.gz
- Upload date:
- Size: 27.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c45915abe4a21520e5ac12fa7e4603dd7e670e7a3de6b9665e89fc7e9a45fece
|
|
| MD5 |
38fdf9216bcdbe30e86622c1afdc6c15
|
|
| BLAKE2b-256 |
d0e5b78663185d442847abeddbe7a5adb0e6bf307928964ea44e7f7433d8a329
|
File details
Details for the file p2d_deepseek-0.2.4-py3-none-any.whl.
File metadata
- Download URL: p2d_deepseek-0.2.4-py3-none-any.whl
- Upload date:
- Size: 31.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
913b6b4a35e964bfd866dc6b958b7f271b8751e1753e493697df0dd6630be54e
|
|
| MD5 |
2868d9dbd1503868d78f0e546024543a
|
|
| BLAKE2b-256 |
b6422e30ad37b64723d28c2a4263ea3a721b9301460297e248b7d72c76c93775
|