Metadata-Version: 2.5
Name: langchain-bitdeer-ai
Version: 0.1.0
Summary: An integration package connecting Bitdeer AI and LangChain
Project-URL: Homepage, https://github.com/BitdeerAI/langchain-bitdeer-ai
Project-URL: Repository, https://github.com/BitdeerAI/langchain-bitdeer-ai
Project-URL: Issues, https://github.com/BitdeerAI/langchain-bitdeer-ai/issues
Project-URL: Changelog, https://github.com/BitdeerAI/langchain-bitdeer-ai/releases
License: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: <4.0.0,>=3.10.0
Requires-Dist: langchain-core<2.0.0,>=1.6.7
Requires-Dist: langchain-openai<1.7.0,>=1.6.7
Description-Content-Type: text/markdown

# langchain-bitdeer-ai

[LangChain](https://www.langchain.com/) integration for [Bitdeer AI](https://www.bitdeer.ai).

> **Status: under development.** Not yet published to PyPI.

This package provides Bitdeer AI [chat models](https://docs.langchain.com/oss/python/langchain/models)
(`ChatBitdeerAI`) and [embeddings](https://docs.langchain.com/oss/python/integrations/embeddings)
(`BitdeerAIEmbeddings`) for LangChain. Setup is shared; the guides for each follow.

## Setup

To access Bitdeer AI models, you need a Bitdeer AI account and an API key, and you need to install the
`langchain-bitdeer-ai` integration package.

### Credentials

Get an API key from Bitdeer AI and set it as the `BITDEER_API_KEY` environment variable:

```python
import getpass
import os

if "BITDEER_API_KEY" not in os.environ:
    os.environ["BITDEER_API_KEY"] = getpass.getpass("Enter your Bitdeer AI API key: ")
```

You can also pass it directly, for example `ChatBitdeerAI(api_key="...")` or
`BitdeerAIEmbeddings(api_key="...")`.

To trace your model calls automatically, set your [LangSmith](https://docs.langchain.com/langsmith/observability)
API key:

```python
os.environ["LANGSMITH_API_KEY"] = getpass.getpass("Enter your LangSmith API key: ")
os.environ["LANGSMITH_TRACING"] = "true"
```

Runs show up in LangSmith under the `bitdeer_ai` provider.

### Installation

The LangChain Bitdeer AI integration lives in the `langchain-bitdeer-ai` package:

```bash
pip install -U langchain-bitdeer-ai
```

```bash
uv add langchain-bitdeer-ai
```

### Custom base URL

| Argument   | Environment variable | Default                                |
| ---------- | -------------------- | -------------------------------------- |
| `api_key`  | `BITDEER_API_KEY`    | none (required)                        |
| `base_url` | `BITDEER_API_BASE`   | `https://api-inference.bitdeer.ai/v1`  |

`ChatBitdeerAI` and `BitdeerAIEmbeddings` read only these variables. They never fall back to
`OPENAI_API_KEY` or `OPENAI_BASE_URL`, so an OpenAI key in your environment is never sent to Bitdeer AI.

## Chat models

### Overview

#### Details

| Class           | Package                | Serializable | JS support |
| :-------------- | :--------------------- | :----------: | :--------: |
| `ChatBitdeerAI` | `langchain-bitdeer-ai` |     beta     |     ❌     |

#### Features

| [Tool calling](https://docs.langchain.com/oss/python/langchain/tools) | [Structured output](https://docs.langchain.com/oss/python/langchain/structured-output) | [Image input](https://docs.langchain.com/oss/python/langchain/messages#multimodal) | Audio input | Video input | [Token-level streaming](https://docs.langchain.com/oss/python/langchain/streaming) | Native async | [Token usage](https://docs.langchain.com/oss/python/langchain/models#token-usage) |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |

Image input depends on the model. The [Bitdeer AI model catalog](https://developers.bitdeer.ai/docs/api/models-catalog)
lists which models accept images.

### Instantiation

The `model` argument is required. Pass any chat model ID from the
[Bitdeer AI model catalog](https://developers.bitdeer.ai/docs/api/models-catalog):

```python
from langchain_bitdeer_ai import ChatBitdeerAI

model = ChatBitdeerAI(
    model="zai-org/GLM-5.3-Flash",
    temperature=0,
    timeout=None,
    max_tokens=1024,
    max_retries=2,
)
```

These and other arguments work as they do in `langchain-openai`, with these exceptions:

- `http_socket_options` is not applied (see [Known issues](#known-issues)).
- `use_responses_api=True` raises `ValueError`, because Bitdeer AI supports Chat Completions only.
- `organization` is not sent, and `OPENAI_ORG_ID` and `OPENAI_PROJECT_ID` are ignored.

### Invocation

```python
messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "What is the capital of France?"},
]
response = model.invoke(messages)

print(response.text)
print(response.content_blocks)
```

Streaming, async, batching, tool calling and structured output work as they do for any LangChain chat
model:

```python
for chunk in model.stream("Write a haiku about the sea."):
    print(chunk.text, end="")
```

### Reasoning output

Bitdeer AI models return their reasoning along with the answer. `ChatBitdeerAI` keeps it in
`additional_kwargs["reasoning_content"]` and in a standard `reasoning` content block:

```python
response = model.invoke("What is 2 + 3?")

print(response.additional_kwargs["reasoning_content"])

for block in response.content_blocks:
    if block["type"] == "reasoning":
        print(block["reasoning"])
```

When you stream, each chunk carries its part of the reasoning, and adding the chunks together gives the
full text.

`ChatBitdeerAI` uses the Chat Completions API. Parameters that only OpenAI's Responses API accepts, such as
`reasoning`, `include` and `truncation`, raise a `ValueError`, and so does `use_responses_api=True`. The Chat Completions parameter that
corresponds to `reasoning={"effort": ...}` is `reasoning_effort`:

```python
model = ChatBitdeerAI(model="zai-org/GLM-5.3-Flash", reasoning_effort="low")
```

### Token usage

`response.usage_metadata` reports input, output and total tokens, and reasoning tokens under
`output_token_details.reasoning`. Token usage is also reported when streaming. Pass `stream_usage=False`
to turn it off.

### Known issues

- `http_socket_options` and the TCP keepalive defaults of `langchain-openai` are not applied. Pass your own
  `http_client` and `http_async_client` if you need them. `openai_proxy` works as in `langchain-openai`
  (it cannot be combined with `http_client`), but `OPENAI_PROXY` is not read.

- With `DeepSeek-V4.1-Flash`, forcing a tool call (as `with_structured_output` does by default) can return
  the tool call as plain text. Use `method="json_schema"` for structured output with this model:

  ```python
  from pydantic import BaseModel


  class Person(BaseModel):
      name: str
      age: int


  model = ChatBitdeerAI(model="deepseek-ai/DeepSeek-V4.1-Flash")
  structured_model = model.with_structured_output(Person, method="json_schema")
  structured_model.invoke("Ada is 36 years old.")
  ```

- Structured output with `method="json_schema"` and JSON mode can print a Pydantic serializer warning
  about a `metadata` field (`PydanticSerializationUnexpectedValue`). The API returns `metadata` in a shape
  the `openai` SDK doesn't expect. The warning is harmless, and the output is correct.

## Embeddings

### Overview

| Class                  | Package                | Serializable | JS support |
| :--------------------- | :--------------------- | :----------: | :--------: |
| `BitdeerAIEmbeddings`  | `langchain-bitdeer-ai` |      ❌      |     ❌     |

`BitdeerAIEmbeddings` calls Bitdeer AI's OpenAI-compatible embeddings endpoint. It currently serves
`BAAI/bge-m3`; see the [Bitdeer AI model catalog](https://developers.bitdeer.ai/docs/api/models-catalog)
for the current list and pricing.

### Instantiation

The `model` argument is required:

```python
from langchain_bitdeer_ai import BitdeerAIEmbeddings

embeddings = BitdeerAIEmbeddings(model="BAAI/bge-m3")
```

These and other arguments work as they do in `langchain-openai`, except the ones listed under
[Limitations](#limitations).

### Usage

```python
vector = embeddings.embed_query("The quick brown fox jumps over the lazy dog.")
print(len(vector))

vectors = embeddings.embed_documents(["Hello", "World"])
print(len(vectors))
```

Async methods (`aembed_query`, `aembed_documents`) are available too. Use the embeddings with any LangChain
vector store, for example `InMemoryVectorStore.from_texts(texts, embeddings)`.

### Limitations

- `http_socket_options` and the TCP keepalive defaults of `langchain-openai` are not applied. Pass your own
  `http_client` and `http_async_client` if you need them. `openai_proxy` works as in `langchain-openai`
  (it cannot be combined with `http_client`), but `OPENAI_PROXY` is not read.
- `openai_organization` is not sent, and `OPENAI_ORG_ID` and `OPENAI_PROJECT_ID` are ignored.
- Texts are sent to the API as plain strings. Unlike `OpenAIEmbeddings`, nothing is tokenized with an
  OpenAI tokenizer, so `check_embedding_ctx_length=True` raises a `ValueError`.
- The endpoint ignores `dimensions`, so passing it raises a `ValueError`.
- Each text can be at most 8192 tokens. Longer input is rejected by the API with a `400` error, and nothing
  checks the length before sending. Split long documents before embedding them.
- `embed_documents` sends texts in batches of `chunk_size` (default 1000) per request. Lower it if requests
  are rejected for being too large.

## Contributing

Issues and pull requests are welcome.

To set up a development environment, install [uv](https://docs.astral.sh/uv/) and run:

```bash
uv sync --all-groups
make lint   # ruff and mypy
make test   # unit tests, no network access
```

Integration tests call the live API and need `BITDEER_API_KEY`. Without it they are skipped:

```bash
make integration_tests
```
