Metadata-Version: 2.4
Name: glchat-sdk
Version: 0.0.14
Summary: GLChat Python Client
Author-email: GDP Labs <jobs@gdplabs.id>
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: httpx>=0.28.1
Requires-Dist: pydantic>=2.0.0
Provides-Extra: evals
Requires-Dist: aioboto3<16.0.0,>=15.2.0; extra == 'evals'
Requires-Dist: gllm-core-binary<0.4.0,>=0.3.0; (python_version < '3.14') and extra == 'evals'
Requires-Dist: gllm-evals-binary[deepeval,langchain,ragas]<0.0.37,>=0.0.36; (python_version < '3.14') and extra == 'evals'
Requires-Dist: langfuse<4.0.0,>=3.2.1; (python_version < '3.14') and extra == 'evals'
Requires-Dist: pillow<12.0.0,>=11.0.0; extra == 'evals'
Requires-Dist: pytest-asyncio<0.24.0,>=0.23.6; extra == 'evals'
Description-Content-Type: text/markdown

<p align="center">
  <a href="https://docs.glair.ai" target="_blank">
    <picture>
      <source media="(prefers-color-scheme: dark)" srcset="https://assets.analytics.glair.ai/generative/img/glchat-beta-dark.svg">
      <source media="(prefers-color-scheme: light)" srcset="https://assets.analytics.glair.ai/generative/img/glchat-beta-light.svg">
      <img alt="GLAIR" src="https://assets.analytics.glair.ai/generative/img/glchat-beta-light.svg" width="180" height="60" style="max-width: 100%;">
    </picture>
  </a>
</p>

<p align="center">
  🤖 GLChat Python SDK 🐍
<p>

<p align="center">
    <a href="https://pypi.org/project/glchat-sdk/"><img src="https://img.shields.io/pypi/v/glchat-sdk" alt="PyPI Latest Release"></a>
    <a href="https://github.com/GDP-ADMIN/glchat-sdk/blob/main/python/glchat-sdk/LICENSE"><img src="https://img.shields.io/pypi/l/glchat-sdk" alt="License"></a>
</p>

A lightweight, flexible Python client for interacting with the GLChat Backend API, providing a simple interface to send messages and receive streaming responses. Built with an OpenAI-like API design for familiarity and ease of use.

## 📋 Overview

GLChat Python Client is a Python library that simplifies interaction with the GLChat service. It provides a clean, intuitive API for sending messages, handling file attachments, and processing streaming responses, enabling rapid development of chat applications.

## 📋 Requirements

**Python 3.11.x** or higher is required.

## ✨ Features

- **🔌 OpenAI-like API**: Familiar interface following the OpenAI SDK pattern
- **🔐 Authentication Support**: Built-in API key authentication
- **🚀 Simple API**: Send messages and receive responses with minimal code
- **⚡ Streaming Support**: Process responses in real-time as they arrive
- **📎 File Integration**: Easily attach and send files with your messages
- **💬 Conversation Management**: Create and manage conversations with chatbots
- **🎯 Type Safety**: Comprehensive type hints for better development experience
- **🔄 Flexible Response Handling**: Choose between streaming or complete text responses
- **💾 Memory Efficient**: Optimized file handling for large files

## 📦 Installation

To install the package:

```bash
pip install glchat-sdk
```

After installation, you can verify it works by trying to import it from any directory:

```python
from glchat_sdk import GLChat
```

## 🚀 Quick Start

Creating a chat client with GLChat is incredibly simple:

```python
from glchat_sdk import GLChat

# Initialize the GLChat client with your API key
client = GLChat(api_key="your-api-key")

# Send a message to the chatbot and receive a streaming response
for chunk in client.message.create(
    application_id="your-application-id",
    message="Hello!"
):
    print(chunk.decode("utf-8"), end="")

# Create a new conversation
conversation = client.conversation.create(
    user_id="your-user-id",
    application_id="your-application-id",
    title="My First Conversation"
)
print(f"Created conversation: {conversation['conversation_id']}")
```

Note: Make sure you have the correct chatbot ID and API key before running example.

### 🔐 Environment Variables

GLChat uses `os.getenv()` to read environment variables. **You are responsible for loading environment variables** in your application before initializing the GLChat client. You can use libraries like `python-dotenv`, `python-decouple`, or set them directly in your shell.

**Available environment variables:**

- `GLCHAT_API_KEY`: Your GLChat API key for authentication
- `GLCHAT_BASE_URL`: Custom base URL for the GLChat API (optional)

**Example using python-dotenv:**

First, install python-dotenv:

```bash
pip install python-dotenv
```

Create a `.env` file:

```bash
GLCHAT_API_KEY=your-api-key
GLCHAT_BASE_URL=https://your-custom-endpoint.com/api/
```

Load environment variables in your code:

```python
from dotenv import load_dotenv
from glchat_sdk import GLChat

# Load environment variables from .env file
load_dotenv()

# Will automatically use environment variables
client = GLChat()
```

**Example using shell export:**

```bash
export GLCHAT_API_KEY="your-api-key"
export GLCHAT_BASE_URL="https://your-custom-endpoint.com/api/"
```

Then initialize the client without parameters:

```python
from glchat_sdk import GLChat

# Will automatically use environment variables
client = GLChat()
```

## 🔧 Advanced Usage

### 📤 Sending Messages with Files

```python
from pathlib import Path
from glchat_sdk import GLChat

client = GLChat(api_key="your-api-key")

# Send message with file attachment
for chunk in client.message.create(
    application_id="your-application-id",
    message="What's in this file?",
    files=[Path("/path/to/your/file.txt")],
    user_id="user@example.com",
    conversation_id="your-conversation-id",
    model_name="openai/gpt-4o-mini"
):
    print(chunk.decode("utf-8"), end="")
```

### 📁 Using Different File Types

```python
from glchat_sdk import GLChat
import io

client = GLChat(api_key="your-api-key")

# File path
file_path = "/path/to/file.txt"

# File-like object
file_obj = io.BytesIO(b"file content")

# Raw bytes
file_bytes = b"file content"

# Send with different file types
for chunk in client.message.create(
    application_id="your-application-id",
    message="Process these files",
    files=[file_path, file_obj, file_bytes]
):
    print(chunk.decode("utf-8"), end="")
```

## 📚 API Reference

### GLChat

The main client class for interacting with the GLChat API.

#### 🔧 Initialization

```python
client = GLChat(
    api_key: str | None = None,
    base_url: str | None = None,
    timeout: float = 60.0,
)
```

**Parameters:**

- `api_key`: Your GLChat API key for authentication (set GLCHAT_API_KEY env var) 🔑
- `base_url`: Custom base URL for the GLChat API (optional, or set GLCHAT_BASE_URL env var) 🌐
- `timeout`: Request timeout in seconds (optional, default: 60.0) ⏱️

#### Methods

##### 💬 message.create

Creates a streaming response from the GLChat API.

```python
response_stream = client.message.create(
    application_id: str | None = None,
    chatbot_id: str | None = None,
    message: str,
    parent_id: str | None = None,
    source: str | None = None,
    quote: str | None = None,
    user_id: str | None = None,
    conversation_id: str | None = None,
    user_message_id: str | None = None,
    assistant_message_id: str | None = None,
    chat_history: str | None = None,
    files: List[Union[str, Path, BinaryIO, bytes]] | None = None,
    stream_id: str | None = None,
    metadata: str | None = None,
    model_name: str | None = None,
    anonymize_em: bool | None = None,
    anonymize_lm: bool | None = None,
    use_cache: bool | None = None,
    search_type: str | None = None,
    agent_id: str | None = None,
    exclude_events: list[str] | None = None,
    stream_message_only: bool | None = None,
    exclude_prefix: bool | None = None,
    include_states: bool | None = None,
    filters: str | None = None,
    stream: bool | None = None,
    extra_headers: dict[str, str] | None = None,
    **kwargs: Any
) -> Iterator[bytes] | MessageResponse
```

**Parameters:**

- `application_id`: Application identifier (recommended) 🆔
- `chatbot_id`: Chatbot identifier - use application_id instead 🤖
- `message`: Required user message 💬
- `parent_id`: Parent message ID for threading (optional) 🧵
- `source`: Source identifier for the message (optional) 📍
- `quote`: Quoted message content (optional) 💭
- `user_id`: User identifier (optional) 👤
- `conversation_id`: Conversation identifier (optional) 💬
- `user_message_id`: User message identifier (optional) 🆔
- `assistant_message_id`: Assistant message identifier (optional) 🤖
- `chat_history`: Chat history context (optional) 📚
- `files`: List of files (filepath, binary, file object, or bytes) (optional) 📎
- `stream_id`: Stream identifier (optional) 🌊
- `metadata`: Additional metadata (optional) 📋
- `model_name`: Model name to use for generation (optional) 🧠
- `anonymize_em`: Whether to anonymize embeddings (optional) 🕵️
- `anonymize_lm`: Whether to anonymize language model (optional) 🕵️
- `use_cache`: Whether to use cached responses (optional) 💾
- `search_type`: Type of search to perform (optional) 🔍
- `agent_id`: Agent identifier to use for the message (optional) 🤖
- `exclude_events`: Defines event types for the event emitter module. (optional) 🚫
- `stream_message_only`: If true, only stream the message content without additional events (optional) 📡
- `exclude_prefix`: If true, exclude the prefix from the response (optional) 🚫
- `include_states`: If true, include state information in the response (optional) 📝
- `filters`: Stringified JSON representing filters to apply (optional) 🗂️
- `stream`: If true or None, returns an iterator for streaming response; if false, returns the complete response at once (optional) 🔄
- `extra_headers`: Additional headers to include in the request (optional) 📋
- `**kwargs`: Additional arbitrary parameters to include in the request payload. Useful if you need to quickly pass parameters currently unsupported in this SDK 📦

**Returns:**

- `Iterator[bytes]`: Streaming response chunks 📡

##### 💬 conversation.create

Creates a new conversation with the GLChat API.

```python
conversation = client.conversation.create(
    user_id: str,
    application_id: str | None = None,
    chatbot_id: str | None = None,
    title: str | None = None,
    model_name: str | None = None,
    extra_headers: dict[str, str] | None = None
) -> dict[str, Any]
```

**Parameters:**

- `user_id`: Required user identifier 👤
- `application_id`: Application identifier (recommended) 🆔
- `chatbot_id`: Chatbot identifier - use application_id instead 🤖
- `title`: Optional conversation title 📝
- `model_name`: Optional model name to use 🧠
- `extra_headers`: Additional headers to include in the request (optional) 📋

**Returns:**

- `dict[str, Any]`: Conversation response data including conversation_id 💬

##### 🤖 chatbots.list

Lists available chatbots from the GLChat API.

```python
chatbots = client.chatbots.list(
    user_id: str | None = None,
    extra_headers: dict[str, str] | None = None
) -> dict[str, Any]
```

**Parameters:**

- `user_id`: Optional user identifier to filter chatbots (optional) 👤
- `extra_headers`: Additional headers to include in the request (optional) 📋

**Returns:**

- `dict[str, Any]`: Dictionary containing chatbots response data 🤖

##### 🔐 auth.whatsapp.register

Registers new users with WhatsApp.

```python
response = client.auth.whatsapp.register(
    whatsapp_id: str,
    email: str,
    profile_name: str | None = None,
    extra_headers: dict[str, str] | None = None
) -> dict[str, Any]
```

**Parameters:**

- `whatsapp_id`: WhatsApp ID of the user (required) 📱
- `email`: Email address of the user (required) 📧
- `profile_name`: Profile name of the user (optional) 👤
- `extra_headers`: Additional headers for WhatsApp authentication and multi-tenancy (optional) 🔑

**Returns:**

- `dict[str, Any]`: Dictionary containing the registration response data ✅

##### 👤 users.verification.username.list

Gets username by phone number.

```python
response = client.users.verification.username.list(
    phone_number: str,
    extra_headers: dict[str, str] | None = None
) -> dict[str, Any]
```

**Parameters:**

- `phone_number`: Phone number in international format (e.g., +62812345678 or 62812345678) (required) 📞
- `extra_headers`: Additional headers for WhatsApp authentication and multi-tenancy (optional) 🔑

**Returns:**

- `dict[str, Any]`: Dictionary containing the username response data 👤

##### 📧 users.verification.resend

Resends verification code.

```python
response = client.users.verification.resend(
    challenge_id: str,
    channel: str,
    extra_headers: dict[str, str] | None = None
) -> dict[str, Any]
```

**Parameters:**

- `challenge_id`: Challenge ID (required) 🆔
- `channel`: Verification channel ("SMS", "WHATSAPP", or "EMAIL") (required) 📱
- `extra_headers`: Additional headers to include in the request (optional) 📋

**Returns:**

- `dict[str, Any]`: Dictionary containing the resend verification response data 📧

##### ❌ users.verification.cancel

Cancels verification challenge.

```python
client.users.verification.cancel(
    challenge_id: str,
    extra_headers: dict[str, str] | None = None
) -> None
```

**Parameters:**

- `challenge_id`: Challenge ID (required) 🆔
- `extra_headers`: Additional headers to include in the request (optional) 📋

**Returns:**

- `None`: No return value

##### ✅ users.verification.verify_and_bind

Verifies OTP code and binds phone or email to user.

```python
client.users.verification.verify_and_bind(
    challenge_id: str,
    code: str,
    extra_headers: dict[str, str] | None = None
) -> None
```

**Parameters:**

- `challenge_id`: Challenge ID (required) 🆔
- `code`: OTP code to verify (required) 🔢
- `extra_headers`: Additional headers to include in the request (optional) 📋

**Returns:**

- `None`: No return value

##### 📨 users.verification.request_verification

Requests phone number or email verification.

```python
response = client.users.verification.request_verification(
    username: str,
    contact: str,
    channel: str,
    extra_headers: dict[str, str] | None = None
) -> dict[str, Any]
```

**Parameters:**

- `username`: Username to bind with phone number (required) 👤
- `contact`: Phone number in international format or email address (required) 📞
- `channel`: Verification channel ("SMS", "WHATSAPP", or "EMAIL") (required) 📱
- `extra_headers`: Additional headers to include in the request (optional) 📋

**Returns:**

- `dict[str, Any]`: Dictionary containing the request verification response data 📨

## 📁 File Support

The client supports various file input types with optimized memory handling:

- **📂 File paths** (string or Path object)
- **💾 Binary data** (bytes)
- **📄 File-like objects** (with read() method) - passed directly to avoid memory issues

## 🔐 Authentication

The client supports API key authentication with flexible configuration options. The API key can be provided either as a parameter during initialization or through environment variables.

### 🔑 API Key Configuration

**Option 1: Direct Parameter**

```python
client = GLChat(api_key="your-api-key")
```

**Option 2: Environment Variable**

```bash
export GLCHAT_API_KEY="your-api-key"
```

```python
client = GLChat()  # Automatically uses GLCHAT_API_KEY environment variable
```

**Option 3: Priority System**

```python
# Parameter takes priority over environment variable
client = GLChat(api_key="explicit-api-key")  # Uses explicit key even if env var is set
```

### 🔒 Authentication Headers

When an API key is provided (via parameter or environment variable), it's automatically included in the Authorization header for all requests:

```python
# API key is automatically used in requests 🔑
client = GLChat(api_key="your-api-key")
for chunk in client.message.create(application_id="your-application-id", message="Hello!"):
    print(chunk.decode("utf-8"), end="")
```

### 📱 WhatsApp API and Multi-Tenancy

For WhatsApp operations and multi-tenant setups, you can pass additional headers using the `extra_headers` parameter:

```python
# WhatsApp user registration with tenant ID
extra_headers = {
    "X-API-Key": "your-whatsapp-api-key",
    "X-Tenant-ID": "your-tenant-id"
}
response = client.auth.whatsapp.register(
    whatsapp_id="62812345678",
    email="user@example.com",
    extra_headers=extra_headers
)

# Get username by phone with WhatsApp API key
extra_headers = {"X-API-Key": "your-whatsapp-api-key"}
response = client.users.verification.username.list(
    phone_number="62812345678",
    extra_headers=extra_headers
)
```

**Note:** The `extra_headers` parameter will override any existing headers with the same key from `default_headers`.

### ⚠️ Required Configuration

**API key is required** - you must provide it either:

- As the `api_key` parameter when initializing the client, OR
- Set the `GLCHAT_API_KEY` environment variable

If neither is provided, the client will raise a `ValueError`:

```python
client = GLChat()  # Raises ValueError if GLCHAT_API_KEY is not set
```

## ⚠️ Error Handling

The client uses `httpx` for HTTP requests and will raise appropriate exceptions for HTTP errors. Make sure to handle these exceptions in your code.
