How to Use DeepSeek with LangChain: A V4 Python Tutorial

Learn how to use DeepSeek with LangChain in Python — install langchain-deepseek, wire up V4 models, build a RAG chain. Start coding now.

Tutorials·April 25, 2026·By DS Guide Editorial
DeepSeek with LangChain title card: eyebrow 'Tutorial · LangChain', title 'DeepSeek + LangChain', tagline 'ChatDeepSeek with deepseek-flash and deepseek-v4-pro.', and four stats: 2 API models, 1M context tokens, 384K max output, and HIGH default effort, beside an open-manual illustration.

You want a working LangChain pipeline that calls DeepSeek’s current models, returns clean structured output, and does not depend on the legacy deepseek-chat ID that has since been retired. That is a fair ask: as of October 2026, LangChain’s own ChatDeepSeek integration page still shows model="deepseek-chat" in its examples. This guide walks through using DeepSeek with LangChain end to end: installing the official langchain-deepseek package, wiring it to deepseek-flash and deepseek-v4-pro, controlling thinking mode, streaming tokens, calling tools, and dropping the model into a small RAG chain. By the end you will have a Python script that runs, plus the rates you need to decide which model to use.

What you’ll build

LangChain patterns title card: eyebrow 'LangChain · Four patterns', title 'Four Chain Patterns', tagline 'Chat, streaming, tool calling and RAG on ChatDeepSeek.', and four stats: 4 patterns shown, 2 model IDs, 3.10 minimum Python, and MIT package licence, beside a punch-cards illustration.

The goal is a single Python project that demonstrates the four patterns most teams actually need: a plain chat call, a streaming call, a tool-calling call, and a retrieval-augmented chain. All four use the same ChatDeepSeek class, just configured differently. You can swap the model= string between deepseek-flash and deepseek-v4-pro without touching anything else.

Before any of that works, two facts to anchor on. First, DeepSeek’s API lists two models today: deepseek-flash, served by DeepSeek-V4.1-Flash-1017 (released 17 October 2026), and deepseek-v4-pro, served by DeepSeek-V4-Pro-0813 (released 13 August 2026). Both publish their weights under the MIT licence. Second, LangChain ships an official integration package, langchain-deepseek (version 1.1.1 as of October 2026). It is published on PyPI under the MIT licence with langchain-core and langchain-openai as required dependencies — and its ChatDeepSeek class subclasses LangChain’s OpenAI chat-model base, BaseChatOpenAI.

Prerequisites

  • Python 3.10 or later (langchain-deepseek 1.1.1 requires it).
  • A DeepSeek API key from the developer console — see our walkthrough on how to get a DeepSeek API key if you have not done it yet.
  • A virtual environment (venv, uv, or poetry) to keep dependencies isolated.
  • A topped-up or granted DeepSeek balance. Calls are billed per token at the rates in Step 3; for scale, the Step 2 call is capped at max_tokens=1024, so its output costs at most 1,024 × $1.20 / 1M ≈ $0.0012 at peak ($0.0006 off-peak), and the Step 4 deepseek-v4-pro call, capped at 16,384 tokens, at most 16,384 × $3.96 / 1M ≈ $0.065 at peak ($0.032 off-peak).
  • Familiarity with the basics of the DeepSeek API getting started flow helps but is not required.

Step 1: Install the LangChain DeepSeek package

The LangChain DeepSeek integration lives in the langchain-deepseek package, which pulls in langchain-core and langchain-openai. Install it with pip in your virtual environment:

pip install -U langchain-deepseek

If you plan to follow the RAG example in Step 5, also install an embedding provider. DeepSeek’s API documents no embeddings endpoint, so pair it with whatever embedder you already use; the example below runs a local sentence-transformers model through langchain-huggingface and keeps vectors in LangChain’s built-in in-memory store:

pip install -U "langchain-huggingface[full]"

Then export your key as the DEEPSEEK_API_KEY environment variable, which ChatDeepSeek reads automatically:

export DEEPSEEK_API_KEY="sk-..."

Step 2: A minimal ChatDeepSeek call

The wrapper class is ChatDeepSeek. Because it inherits from BaseChatOpenAI, the OpenAI-style arguments you already know — temperature, max_tokens, timeout, reasoning_effort, extra_body — work as expected. The class reads the API key from DEEPSEEK_API_KEY and points at DeepSeek’s OpenAI-compatible base URL by default (override it with api_base or the DEEPSEEK_API_BASE variable), so the only required argument is the model name.

Thinking mode is on by default for both DeepSeek models. For a plain chat call, switch it off through extra_body — DeepSeek’s documented field — which also makes temperature take effect. Save this as quickstart.py:

from langchain_deepseek import ChatDeepSeek

llm = ChatDeepSeek(
    model="deepseek-flash",
    temperature=1.3,  # DeepSeek's suggestion for general conversation
    max_tokens=1024,
    timeout=60,
    max_retries=2,
    extra_body={"thinking": {"type": "disabled"}},  # plain chat, no reasoning step
)

messages = [
    ("system", "You are a concise technical writer."),
    ("human", "Explain MoE routing in two sentences."),
]

response = llm.invoke(messages)
print(response.content)
print(response.usage_metadata)

Two things to notice. The messages list uses LangChain’s tuple shorthand instead of explicit HumanMessage / SystemMessage objects — both work, the tuples are shorter. And response.usage_metadata returns a dict like {"input_tokens": …, "output_tokens": …, "total_tokens": …}; langchain-deepseek also copies DeepSeek’s cache-hit count into input_token_details["cache_read"], which is what you wire into your own cost tracking.

Behind the wrapper, every call hits POST /chat/completions on DeepSeek’s OpenAI-compatible endpoint. The API is stateless: DeepSeek does not remember prior turns server-side, so multi-turn conversations require resending the full messages array every request. The web chat and mobile app behave differently — they keep session history for you. The API does not.

Step 3: Pick the right model

The two models share the same API surface and the same 1M-token context window with up to 384K output tokens. The differences are price, concurrency and image input. Here are the current rates, per 1M tokens, from the official DeepSeek pricing page as of October 2026 (peak / off-peak):

Model Input cache hit Input cache miss Output Notes
deepseek-flash $0.006 / $0.003 $0.30 / $0.15 $1.20 / $0.60 Default choice; the only current model with image input; 2,500 concurrent requests per account
deepseek-v4-pro $0.044 / $0.022 $1.32 / $0.66 $3.96 / $1.98 3.3× Flash’s output price; no image input; 500 concurrent requests per account

Peak hours are 01:00–04:00 and 06:00–10:00 UTC, Monday to Friday, excluding Chinese public holidays; every other hour bills at the off-peak rate. DeepSeek’s 10 September 2026 release note for V4.1-Flash says tests by multiple parties put it ahead of V4-Pro on performance, cost, speed and total runtime, so start with deepseek-flash and switch only where your own evaluation favours Pro. For worked cost examples, see our DeepSeek API pricing page or run the numbers in our DeepSeek pricing calculator.

Step 4: Enable thinking mode

Diagram of a LangChain pipeline: PromptTemplate feeds ChatDeepSeek (V4-Pro) with an extra_body thinking setting, which produces reasoning text and an answer that pass to a StrOutputParser.

Thinking mode is a request parameter, not a separate model ID, and both models support it. It is on by default with effort high; with LangChain you set the effort through reasoning_effort ("low", "high" or "max") and the toggle through extra_body, exactly as with the OpenAI SDK:

from langchain_deepseek import ChatDeepSeek

reasoner = ChatDeepSeek(
    model="deepseek-v4-pro",
    max_tokens=16384,
    reasoning_effort="high",
    extra_body={"thinking": {"type": "enabled"}},
)

result = reasoner.invoke(
    "A train leaves Chicago at 9:00 averaging 60 mph. "
    "A second train leaves the same station at 10:30 averaging 80 mph "
    "on a parallel track. When does the second train catch up?"
)

print("Reasoning content:")
print(result.additional_kwargs.get("reasoning_content"))
print("\nAnswer:")
print(result.content)

Thinking-mode responses return reasoning_content alongside the final content. langchain-deepseek copies the reasoning text to response.additional_kwargs["reasoning_content"]; the user-facing answer stays on response.content. DeepSeek’s guidance is low for simple tasks, high for daily agent tasks and max for more complex scenarios.

Temperature guidance from DeepSeek’s own documentation, which is worth pinning to the wall — it applies to non-thinking mode only, because in thinking mode temperature is accepted but has no effect:

  • 0.0 — code generation, math
  • 1.0 — data analysis, cleaning
  • 1.3 — general conversation, translation
  • 1.5 — creative writing

Legacy IDs in older examples

Older codebases, and LangChain’s integration page as of October 2026, still show deepseek-chat or deepseek-reasoner, together with a note that deepseek-reasoner does not support tool calling or structured output. That guidance reflects the V3 era. Both legacy IDs were scheduled for retirement on 2026-07-24 at 15:59 UTC and are no longer listed in DeepSeek’s docs, so replace them with deepseek-flash or deepseek-v4-pro. The name deepseek-v4-flash is still accepted, but DeepSeek serves it with DeepSeek-V4.1-Flash-1017 at the Flash price. Today both models support tool calls in thinking and non-thinking mode. Migration is a one-line model= swap; the base URL does not change.

Step 5: Streaming, tool calling, and RAG

Streaming tokens

For chat UIs, streaming is non-negotiable. ChatDeepSeek implements LangChain’s standard .stream() method; with the non-thinking llm from Step 2:

for chunk in llm.stream("Write a haiku about MoE routing."):
    print(chunk.content, end="", flush=True)

When thinking mode is enabled, reasoning chunks stream before the answer chunks; each one carries its text in chunk.additional_kwargs["reasoning_content"], so you can separate the two:

for chunk in reasoner.stream("Is 1,001 a prime number?"):
    reasoning = chunk.additional_kwargs.get("reasoning_content")
    if reasoning:
        print(reasoning, end="", flush=True)
    elif chunk.content:
        print(chunk.content, end="", flush=True)

Tool calling

ChatDeepSeek exposes bind_tools() in the standard LangChain shape. One DeepSeek-specific rule matters here: in thinking mode, a request that carries tools must pass back the reasoning_content of all earlier assistant turns, or the API returns a 400 error. The langchain-deepseek 1.1.1 source does not add reasoning_content when it serialises earlier assistant messages, so for multi-step tool loops through ChatDeepSeek, disable thinking:

from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langchain_deepseek import ChatDeepSeek

@tool
def get_weather(city: str) -> str:
    """Return current weather for a city."""
    return f"{city}: 14°C, light rain"  # stub: call a real weather API here

tool_llm = ChatDeepSeek(
    model="deepseek-flash",
    extra_body={"thinking": {"type": "disabled"}},
)
agent = tool_llm.bind_tools([get_weather])

messages = [HumanMessage("What's the weather in Manchester?")]
ai_msg = agent.invoke(messages)
print(ai_msg.tool_calls)
messages.append(ai_msg)

for call in ai_msg.tool_calls:
    messages.append(get_weather.invoke(call))  # returns a ToolMessage

final = agent.invoke(messages)
print(final.content)

A note on strict=True: DeepSeek’s strict mode for tool schemas is a Beta feature served from https://api.deepseek.com/beta, and ChatDeepSeek switches to that endpoint automatically when you pass strict=True to bind_tools() or with_structured_output() (provided api_base is left at its default). Per the langchain-deepseek docstring, strict mode requires every object property to be marked as required in the schema.

Structured JSON output

with_structured_output() uses DeepSeek’s tool calling by default (method="function_calling"; "json_schema" is mapped to it) or JSON mode with method="json_mode":

from pydantic import BaseModel, Field

class Ticket(BaseModel):
    """Fields extracted from a support message."""
    product: str = Field(description="Product or page affected")
    severity: int = Field(description="1 (low) to 5 (critical)")

extractor = tool_llm.with_structured_output(Ticket)
print(extractor.invoke("The billing page has crashed for every customer since 9am."))

For response_format={"type": "json_object"} (JSON mode), remember that it is designed to return valid JSON, not guaranteed. Always include the word “json” plus a small example schema in the prompt, and set max_tokens high enough that the response cannot truncate mid-object. DeepSeek’s docs also note that the API may occasionally return empty content — handle that path explicitly. Our DeepSeek API JSON mode guide covers the gotchas.

A small RAG chain

Drop ChatDeepSeek into a standard LangChain retriever + prompt + LLM pipeline. Because DeepSeek has no first-party embeddings API, pair it with a separate embedder (here, a local sentence-transformers model). For a deeper walkthrough see our DeepSeek RAG tutorial.

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_deepseek import ChatDeepSeek
from langchain_huggingface import HuggingFaceEmbeddings

docs = [
    "The deepseek-flash API model is served by DeepSeek-V4.1-Flash-1017.",
    "The deepseek-v4-pro API model is served by DeepSeek-V4-Pro-0813.",
    "Both DeepSeek API models have a 1M-token context window.",
]

emb = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
store = InMemoryVectorStore.from_texts(docs, emb)
retriever = store.as_retriever(search_kwargs={"k": 2})

def format_docs(found):
    return "\n".join(doc.page_content for doc in found)

prompt = ChatPromptTemplate.from_messages([
    ("system", "Answer using only the provided context. If unsure, say so."),
    ("human", "Context:\n{context}\n\nQuestion: {question}"),
])

llm = ChatDeepSeek(
    model="deepseek-flash",
    extra_body={"thinking": {"type": "disabled"}},
)

chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

print(chain.invoke("Which model serves deepseek-flash?"))

Step 6: Verify it worked

A working integration shows three signs:

  1. response.content contains a sensible answer.
  2. response.usage_metadata is populated with non-zero token counts.
  3. response.response_metadata["model_name"] shows the model name the API reported back.

If you enabled thinking mode, also check that additional_kwargs["reasoning_content"] is non-empty. If it is missing, check that no extra_body={"thinking": {"type": "disabled"}} is set on that model instance.

Common errors and fixes

Symptom Likely cause Fix
AuthenticationError: 401 Missing or wrong API key Re-export DEEPSEEK_API_KEY; restart the shell
400 or 422 error that mentions the model Typo in the model name, or a retired legacy name such as deepseek-chat Use deepseek-flash or deepseek-v4-pro
400 error on the second turn of a tool loop Thinking mode with tools, and earlier reasoning_content not passed back Disable thinking for that model instance (see Step 5)
Empty content in JSON mode Prompt missing the word “json”, or max_tokens too low Add a JSON schema example; raise max_tokens
Answer cut off (finish_reason is "length") max_tokens too low for a long reasoning-plus-answer response Raise max_tokens (output can reach 384K tokens) or lower reasoning_effort
Tool not called Tool description too vague Rewrite the docstring to describe when to use the tool
RateLimitError (HTTP 429) More concurrent requests than your account’s limit (2,500 for deepseek-flash, 500 for deepseek-v4-pro) Retry with exponential backoff; see DeepSeek API rate limits

When LangChain is the wrong choice

Not every DeepSeek project benefits from a LangChain wrapper. For a single-shot API call from a script or a Lambda, the official OpenAI SDK pointed at https://api.deepseek.com is shorter and has fewer moving parts. LangChain earns its weight when you need orchestration: chains of prompts, retrievers, tool calls, agents, memory, or the LangSmith tracing surface. If your project is “send one prompt, get one answer”, call POST /chat/completions directly. If your project is “retrieve, plan, call tools, summarise”, LangChain saves real time.

Next steps

Diagram of a LangChain data flow: input, prompt template, DeepSeek model, output parser and result, with optional memory feeding the prompt and tools attached to the model.

Frequently asked questions

How do I install the LangChain DeepSeek integration?

Run pip install -U langchain-deepseek in a Python 3.10+ virtual environment, then export your key as DEEPSEEK_API_KEY. The package pulls in langchain-core and langchain-openai as dependencies. Import ChatDeepSeek from langchain_deepseek. For a full walkthrough including how to obtain a key, see our guide on getting a DeepSeek API key.

What model name should I use with ChatDeepSeek today?

Use deepseek-flash (DeepSeek-V4.1-Flash-1017) as the default, or deepseek-v4-pro (DeepSeek-V4-Pro-0813), which costs 3.3× as much per output token. The legacy IDs deepseek-chat and deepseek-reasoner were scheduled for retirement on 2026-07-24 at 15:59 UTC and are no longer listed in DeepSeek’s docs, so update any code that still uses them. See the DeepSeek V4 overview for background.

Does ChatDeepSeek support tool calling and structured output?

Yes, on both current models. ChatDeepSeek provides bind_tools() and with_structured_output(), which use DeepSeek’s tool calling or JSON mode. Passing strict=True sends the request to DeepSeek’s Beta endpoint for strict schema adherence. For multi-step tool loops, disable thinking on that model instance, because thinking mode with tools requires earlier reasoning_content to be passed back. For deeper detail, browse our DeepSeek API function calling reference.

Can I run DeepSeek with LangChain locally instead of via the API?

Yes, with limits. The current open weights are MIT-licensed but large — about 510 GB for DeepSeek-V4.1-Flash-1017 and about 893 GB for DeepSeek-V4-Pro-0813 on Hugging Face — so they need a multi-GPU server running vLLM or SGLang; point ChatDeepSeek at that server’s OpenAI-compatible endpoint with api_base. On a laptop, the smaller DeepSeek R1 distills run in Ollama and plug into LangChain through ChatOllama; note that Ollama’s deepseek-v4.1-flash and deepseek-v4-pro tags are cloud-only. Our running DeepSeek on Ollama walkthrough covers the setup.

How much does a typical LangChain + DeepSeek workflow cost?

LangChain adds no fee of its own; you pay DeepSeek’s per-token rates. As of October 2026, deepseek-flash costs $0.006 (cache hit), $0.30 (cache miss) and $1.20 (output) per 1M tokens at peak, and $0.003, $0.15 and $0.60 off-peak; deepseek-v4-pro costs $0.044, $1.32 and $3.96 at peak, and $0.022, $0.66 and $1.98 off-peak. Multiply each token bucket from usage_metadata by its rate, or estimate your own numbers with our DeepSeek pricing calculator.

Sources & Methodology

Last source check: October 25, 2026

Primary sources

Technical references

Methodology

Information in this article was checked against the official DeepSeek documentation, the article's cited primary sources, and the deepseekai.guide pricing dataset on the date shown above. Where third-party numbers are quoted, the originating source is linked inline so the reader can verify and re-check.

Data confidence

High for facts cited inline with primary-source links; medium for context narrative.

Editorial note

AI tooling moves quickly. If you spot an outdated number or a broken link, the contact link in the footer is the fastest way to flag it for the editorial team.

Leave a Reply

Your email address will not be published. Required fields are marked *