Metadata-Version: 2.4
Name: chat-completions-conversation-with-tools
Version: 0.1.0a1
Summary: Chat Completions conversation helper with tool support for Python 2 and Python 3.
Author-email: Jifeng Wu <jifengwu2k@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/jifengwu2k/chat-completions-conversation-with-tools
Project-URL: Bug Tracker, https://github.com/jifengwu2k/chat-completions-conversation-with-tools/issues
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=2
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: create-inspect-typeddict
Requires-Dist: get-args-and-origin
Requires-Dist: typing; python_version < "3.5"
Dynamic: license-file

# Chat Completions Conversation With Tools

**LLM tool calling for environments that modern SDKs left behind.**

Python 2. Python 3. Windows XP. Embedded CPython. Air-gapped networks. If you can run `import json`, you can run this package.

---

## Why This Exists

LLM tool calling has become essential — but the ecosystem has raced toward modern stacks. The official OpenAI Python SDK requires Python 3.8+. LangChain wants 3.9+. They pull in `httpx`, `pydantic`, `anyio`, and a tree of native extensions. None of that runs on a Windows XP box or an old Debian embedded controller.

Meanwhile, the API itself hasn't changed: it's still JSON over HTTP, with the same OpenAI Chat Completions function-calling schema that every major provider speaks. The barrier is purely in the client libraries.

This package removes that barrier. It's a single-file, zero-dependency stateful wrapper around any OpenAI Chat Completions-compatible HTTP API. No `requests`. No `httpx`. No native code. Just `urllib`/`urllib2`, `json`, and the typing module — all of which ship with Python 2.7 and later.

## What It Gives You

| Capability | How |
|---|---|
| Tool definitions | Python `TypedDict` → JSON Schema (automatic, recursive, cycle-safe) |
| Non-streaming responses | Parse `tool_calls[]` from the response message |
| Streaming responses | SSE line-by-line parser with tool call delta accumulation |
| Conversation history | Automatic message bookkeeping (user, assistant, tool roles) |
| Multimodal | Image URLs via OpenAI content-parts format |
| Providers | OpenAI, DeepSeek, Groq, Together, xAI, OpenRouter — anything that speaks Chat Completions |

## Function Calling Convention

This package implements the **OpenAI native function calling convention** — an API-level protocol where tools and tool calls are first-class fields in the request/response schema. This is the industry standard, supported by OpenAI and every major compatible provider.

### Request format

```json
{
  "model": "...",
  "messages": [...],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Look up the weather for a city.",
        "parameters": {
          "type": "object",
          "properties": {
            "city": { "type": "string" }
          },
          "required": ["city"],
          "additionalProperties": false
        }
      }
    }
  ],
  "tool_choice": "auto"
}
```

### Response format

```json
{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "call_abc123",
          "type": "function",
          "function": {
            "name": "get_weather",
            "arguments": "{\"city\":\"Tokyo\"}"
          }
        }
      ]
    }
  }]
}
```

### Tool results

```json
{
  "role": "tool",
  "tool_call_id": "call_abc123",
  "content": "Sunny, 22°C"
}
```

### Streaming deltas

When `stream: true`, tool calls arrive incrementally via SSE chunks keyed by `index`. The package accumulates fragments and finalizes them when the stream completes.

This is an **API-native** convention — tools and tool calls are structural fields in the HTTP bodies, not prompt-engineered text that requires client-side regex or XML parsing.

## Installation

```bash
pip install chat-completions-conversation-with-tools
```

No native extensions. No system dependencies. Works with pip on Python 2.7 and Python 3.x.

## Usage

Actual tool execution is application-defined. Provide a callable per tool, or bridge to an external process — the package only handles the API conversation.

### Basic example (OpenAI)

```python
from typing import TypedDict
from chat_completions_conversation_with_tools import (
    ChatCompletionsConversationWithTools,
    Tool,
)

class WeatherArgs(TypedDict):
    city: str

conversation = ChatCompletionsConversationWithTools(
    api_key="sk-...",
    base_url="https://api.openai.com/v1",
    model="gpt-4o",
    system_prompt="Call the get_weather tool when asked about weather.",
    tools_by_name={
        "get_weather": Tool("Look up the current weather for a city.", WeatherArgs),
    },
)

# Non-streaming
response = conversation.send_and_receive_response("What's the weather in Tokyo?")
print(response.tool_calls)
# [ToolCall(id='...', name='get_weather', arguments={'city': 'Tokyo'})]

conversation.append_tool_message(response.tool_calls[0].id, "Sunny, 22°C")
final = conversation.send_and_receive_response()
print(final.content)
# "The weather in Tokyo is sunny with a temperature of 22°C."
```

### Streaming (DeepSeek)

```python
conversation = ChatCompletionsConversationWithTools(
    api_key="sk-...",
    base_url="https://api.deepseek.com",
    model="deepseek-chat",
    system_prompt="Call the get_weather tool when asked about weather.",
    tools_by_name={
        "get_weather": Tool("Look up the current weather for a city.", WeatherArgs),
    },
)

response = conversation.send_and_stream_response(
    text="What's the weather in Tokyo?",
    on_content_delta=lambda text: print(text, end="", flush=True),
)
# Let me check the weather in Tokyo for you!
print(response.tool_calls)
# [ToolCall(id='call_00_...', name='get_weather', arguments={'city': 'Tokyo'})]

conversation.append_tool_message(response.tool_calls[0].id, "Sunny, 22°C")
final = conversation.send_and_stream_response(
    on_content_delta=lambda text: print(text, end="", flush=True),
)
# The weather in Tokyo is sunny with a temperature of 22°C.
```

### Image input

```python
conversation.append_user_message(
    "What's in this image?",
    image_url="https://example.com/photo.jpg",
)
response = conversation.send_and_receive_response()
```

## Public API

| Class | Purpose |
|---|---|
| `Tool(description, typeddict_class)` | Wraps a tool description and a TypedDict parameter schema. Automatically converts the TypedDict to JSON Schema. |
| `ToolCall` | Stores a parsed tool call: `.id`, `.name`, `.arguments`. |
| `AssistantResponse` | Stores the assistant turn: `.content` (text) and `.tool_calls` (list of `ToolCall`). |
| `ChatCompletionsConversationWithTools` | Manages message history and API communication. |

### `ChatCompletionsConversationWithTools`

```
__init__(api_key, base_url, model, system_prompt, tools_by_name)
```

| Method | Description |
|---|---|
| `send_and_receive_response(text=None, image_url=None)` | Send a user message (or continue after tool results) and return the assistant response. |
| `send_and_stream_response(text=None, image_url=None, on_content_delta=None, on_tool_call_delta=None)` | Same, but streams the response via callbacks. |
| `append_user_message(text, image_url=None)` | Add a user message to history without sending. |
| `append_assistant_message(content, tool_calls=None)` | Add an assistant message to history. |
| `append_tool_message(tool_call_id, content)` | Add a tool result to history. |
| `reset()` | Clear conversation history and re-insert the system prompt. |
| `set_system_prompt(prompt, update_messages=True)` | Change the system prompt. |
| `build_payload(messages, stream=False)` | Build the raw API request payload (for debugging or custom transport). |

## Constraints (By Design)

**No `requests` or `httpx`.** This package uses only `urllib`/`urllib2` from the standard library. It installs and runs anywhere Python does — no native compilation, no system libraries.

**Python 2.7 compatible.** Every string, every import, every type annotation is written to work on Python 2.7 and Python 3.x from the same source file.

**Single file.** The entire library is one module. Drop `chat_completions_conversation_with_tools.py` into any project. No package structure to navigate, no implicit namespace dependencies.

**No framework dependencies.** No `pydantic`, no `attrs`, no `dataclasses`. Tool parameter schemas use standard `TypedDict`, automatically converted to JSON Schema at call time.

## Provider Compatibility

Tested and working with:

| Provider | Model | Status |
|---|---|---|
| DeepSeek | `deepseek-chat` | ✅ Non-streaming + streaming |
| OpenAI | `gpt-4o`, `gpt-4.1` | ✅ Compatible API |
| Groq, Together, xAI, OpenRouter | Any Chat Completions model | ✅ Compatible API |
| Local (vLLM, Ollama, LM Studio) | Any tool-capable model | ✅ Compatible API |

## License

MIT — see [LICENSE](LICENSE).
