Metadata-Version: 2.4
Name: chatbot-oai
Version: 0.1.0
Summary: An out-of-the-box chat module supporting custom tool registration and invocation via OpenAI SDK
Project-URL: Homepage, https://github.com/axwhizee/chatbot-oai
Project-URL: Issues, https://github.com/axwhizee/chatbot-oai/issues
Author-email: OwlCat <richardcoles@qq.com>
License-Expression: MIT
License-File: LICENSE
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: openai>=2.0
Description-Content-Type: text/markdown

# chatbot-oai

> An out-of-the-box, lightweight Python module for building production-ready chat applications with [OpenAI-compatible APIs](https://github.com/openai/openai-python). Features automatic tool/function calling, multi-round reasoning loops, flexible provider routing, and built-in token tracking.

## 📦 Installation

```bash
pip install chatbot-oai
```

## 🚀 Quick Start

A minimal working example using the default OpenRouter endpoint:

```python
from chatbot_oai import BotConfig, ChatBot

bot = ChatBot(BotConfig())
response = bot.chat("Hello! Please introduce yourself briefly.")
print(response)
```

## ⚙️ Configuration

All settings are managed through the `BotConfig` dataclass. When instantiated without arguments, it defaults to the OpenRouter platform and reads `OPENROUTER_API_KEY` from your environment.

### 🔑 Environment Variable Setup

The module validates that your API key exists at initialization. Set it in your shell:

```bash
export OPENROUTER_API_KEY="sk-xxxxxxxx"
```

It's NOT recommanded to deliver API Key directly like `bot_config = BotConfig(api_key="sk-xxxxxxxx")`

### 🌍 Multi-Provider Support

Switch endpoints effortlessly by overriding `base_url`, `env_key_name`, and `model`. Provider-specific quirks can be passed via `extra_body`.

#### Local vLLM / llama.cpp

```python
bot_conf = BotConfig(
    base_url="http://localhost:9998/v1",
    env_key_name="PRIVATE_KEY",  # Many local servers require a dummy key
    model="Qwopus-9B-Q4_K_M.gguf",
)
```

#### DeepSeek Official API

```python
bot_conf = BotConfig(
    base_url="https://api.deepseek.com",
    env_key_name="DEEPSEEK_API_KEY",
    model="deepseek-v4-flash",
    extra_body={"thinking": {"type": "disabled"}},
)
```

### 🛠️ Custom Tools & Automatic Function Calling

Register Python functions as tools. The LLM will automatically decide when to call them, the module will execute them, feed results back into the context, and continue until no more tools are requested.

#### 1. Define Your Function

```python
def get_weather(city: str) -> str:
    """Returns weather information for a given city."""
    # Replace with real API call or mock data
    return f"The weather in {city} is currently sunny with 22°C."
```

#### 2. Create the OpenAI-Compatible Schema

```python
weather_schema = {
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Retrieve current weather conditions for a specific city.",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name"}
            },
            "required": ["city"]
        }
    }
}
```

#### 3. Register & Use

```python
bot = ChatBot(BotConfig())
bot.tool_register(weather_schema, "get_weather", get_weather)

response = bot.chat("What's the weather in Beijing?")
print(response)
```

#### 🔁 How It Works Internally

1. User sends a prompt → LLM decides whether to call a tool
2. If tool calls are returned, `_tools_handler()` parses JSON arguments, executes the registered Python callable, and captures output
3. Failed executions are caught, logged, and returned as structured error strings so the LLM can self-correct
4. Tool results are appended to conversation history as `"tool"` role messages
5. Loop continues until `completion.choices[0].message.tool_calls` is empty or `max_tool_call_round` is reached

> You can use `bot.context` to show full chat contexts.

## 📄 License

MIT
