Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

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 event parser (CRLF, comments, [DONE]) 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

{
  "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

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

Tool results

{
  "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.

The stream is parsed as SSE events, not raw lines: \r\n and \n endings, comment lines, data: fields with or without a space, multi-line data blocks, and the data: [DONE] sentinel are all handled. Servers that omit the blank line between JSON events are tolerated, and chunks without a choices entry (for example usage payloads) are skipped.

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

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)

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)

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

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.
to_messages() Return a deep, JSON-safe copy of the conversation 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).

Saving and loading conversations

The conversation history is a plain JSON-safe list of messages, so it can be saved and restored without any custom wire format. Serialization and deserialization between this list and files or strings is left to the standard library json module.

import json

from chat_completions_conversation_with_tools import (
    ChatCompletionsConversationWithTools,
    append_messages,
    load_messages,
)

conversation = ChatCompletionsConversationWithTools(
    api_key="...", base_url="...", model="...",
    system_prompt="...", tools_by_name={},
)
conversation.append_user_message("Hello")

# Save: get a deep, JSON-safe copy of the history...
messages = conversation.to_messages()

# ...and hand it to the json module for file or string I/O.
with open("conversation.json", "w", encoding="utf-8") as handle:
    json.dump(messages, handle, indent=2)

# Load: deserialize with json, validate with load_messages...
with open("conversation.json", "r", encoding="utf-8") as handle:
    saved = load_messages(json.load(handle))

# ...and append it to a new (or existing) conversation to continue it.
continued = ChatCompletionsConversationWithTools(
    api_key="...", base_url="...", model="...",
    system_prompt="...", tools_by_name={},
)
append_messages(continued, saved)

The file format is a bare JSON array of API message objects, so a saved transcript can also be used directly as an API request body.

Function Description
conversation.to_messages() Deep, JSON-safe copy of the history (mutating it does not affect the conversation).
load_messages(messages) Validate a message list (roles, tool_call_id on tool messages) and return an independent copy.
append_messages(conversation, messages) Append validated messages to a conversation; skips system messages, since the system prompt is configured at construction. Returns the number of messages appended.

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.

Release files for chat-completions-conversation-with-tools 0.1.0a3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for chat-completions-conversation-with-tools 0.1.0a3
File Size Uploaded
chat_completions_conversation_with_tools-0.1.0a3.tar.gz 15.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for chat-completions-conversation-with-tools 0.1.0a3
File Interpreter ABI Platform
chat_completions_conversation_with_tools-0.1.0a3-py2.py3-none-any.whl Python 3, Python 2 none any Details

Total release size: 26.9 kB

Release files / chat_completions_conversation_with_tools-0.1.0a3.tar.gz

Download URL chat_completions_conversation_with_tools-0.1.0a3.tar.gz
Size 15.4 kB
Tags Source
SHA-256 checksum
How to use checksums
27e696d255dfad8bb8a4a2b3e96a89efa15f497510668345756a445b716d6aa6
BLAKE2b-256 checksum
How to use checksums
8cb81aceffc879efd7c63a2dcb61225c3b6e26c8a58945e61b4cc04116e2df94
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.13

Release files / chat_completions_conversation_with_tools-0.1.0a3-py2.py3-none-any.whl

Download URL chat_completions_conversation_with_tools-0.1.0a3-py2.py3-none-any.whl
Size 11.6 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
3fe710817120a13bdc67b7baf18f7dc8121eda30548b860110bdb015770c28de
BLAKE2b-256 checksum
How to use checksums
9c9dea1eaa1b7dbe605ca5fea697150f58a57674828118bc0f3800a6654470b8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.13
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page