Skip to main content

A lightweight Model I/O normalization layer for OpenAI-compatible LLM calls.

Project description

llm-io-normalizer

llm-io-normalizer Logo

PyPI version Python version License: MIT

llm-io-normalizer is a lightweight, production-ready Model I/O normalization layer for OpenAI-compatible LLM APIs.

As reasoning models (like DeepSeek) become mainstream, the way providers return "thoughts" is highly fragmented. Some use native reasoning_content fields, while others mix <think>...</think> tags directly into the final output. Furthermore, stream interruptions and missing tags often leak private reasoning logic into user-facing answers.

llm-io-normalizer acts as an impenetrable shield. It provides a rock-solid, unified data contract so your business code never has to parse a <think> tag, handle broken streams, or guess provider-specific fields again.

This package is intentionally not a full API gateway. It does not implement authentication, rate limiting, or billing. It focuses purely on the reusable Python SDK layer for Model I/O normalization and reasoning extraction.


Quick Start

With llm-io-normalizer you can easily setup your LLM calls, reliably extract the reasoning process, and get a clean final answer without worrying about provider differences.

  1. Install the SDK
pip install llm-io-normalizer
  1. Setup your Gateway, Request, and generate responses
import asyncio
from llm_io_normalizer import LLMGateway, LLMRequest

async def main():
    # 1. Initialize your Gateway
    gateway = LLMGateway()

    # 2. Make a request (Handles stream/non-stream & tag extraction automatically)
    result = await gateway.generate(
        LLMRequest(
            model_name="deepseek-reasoner",
            base_url="[https://api.your-provider.com/v1](https://api.your-provider.com/v1)",
            api_key="YOUR_API_KEY",
            messages=[{"role": "user", "content": "Explain quantum physics."}],
            stream=True,           # Streams by default
            enable_thinking=True,  # Enable reasoning if supported
        )
    )

    # 3. Ensure the call was successful
    result.require_ok()
    
    # Get Clean Reasoning Output
    print(f"Thoughts: {result.reasoning_text}") 
    
    # Get Clean Final Answer (Guaranteed no <think> tags!)
    print(f"Answer: {result.answer_text}")

asyncio.run(main())
  1. Leverage the JSON output helper
from llm_io_normalizer.normalizers import extract_json_object

# Easily extract strict JSON from markdown-wrapped LLM responses
obj = extract_json_object('Here is the result: ```json\n{"score": 95}\n```')
assert obj == {"score": 95}

Project Structure

Usage

The public contract of LLMResult is intentionally small, stable, and predictable. Business code should depend on these normalized fields instead of reading raw provider fields directly.

result.answer_text       # Pure, final answer content
result.reasoning_text    # Extracted thinking process
result.ok                # True if a valid answer was extracted
result.error_type        # Categorized error (e.g., "EMPTY_ANSWER")
result.error_message     # Detailed error description

Local Development

Below is a guide on setting up a local environment for contributing to or testing the llm-io-normalizer package.

Prerequisites and Dependencies

The project is developed using python. The minimum python version required is 3.10.

Setup

  1. Clone the repository
git clone https://github.com/wanghesong2019/llm-io-normalizer.git
cd llm-io-normalizer
  1. Install development dependencies It is recommended to use a virtual environment. Once activated, install the package in editable mode along with testing tools:
pip install -e ".[dev]"
  1. Run the test suite The project includes a robust suite of unit and mock tests to ensure the extraction engine and fallback mechanisms work flawlessly.
# Run linting
ruff check .

# Run tests
pytest -v

Configuration

llm-io-normalizer uses a flexible configuration system that allows you to adapt to any OpenAI-compatible provider without waiting for library updates.

Provider Field Mapping

Different proxy providers use different fields (reasoning_content, thoughts, reason) to return intermediate thoughts. You can seamlessly map any provider's custom schema by passing a ProviderFieldMapping during initialization:

from llm_io_normalizer import OpenAICompatibleGateway, ProviderFieldMapping

# Define custom fields for a new provider
custom_mapping = ProviderFieldMapping(
    content_fields=("message_body", "text"),
    reasoning_fields=("chain_of_thought", "deep_thought")
)

# Initialize gateway with custom mapping
gateway = OpenAICompatibleGateway(field_mapping=custom_mapping)

Architecture

llm-io-normalizer Architecture

Reasoning Extraction Engine

The internal normalizer algorithm is rigorously tested against edge cases to ensure private reasoning logic never leaks into user-facing answers. It handles:

  • Multiple Tags: Alternating between thoughts and answers multiple times.
  • Unclosed Tags: Stream interruptions or token limits that leave unclosed <think> tags at the end of the text.
  • Redundant Data: Mixes of native provider reasoning fields with redundant text tags in the main content.

The engine processes these anomalies and produces clean answer_text and reasoning_text strings.

Resilience & Fallback

Network jitter and API quirks often result in dropped tokens or stuck states. llm-io-normalizer implements an automatic safety net (which can be toggled via LLMRequest parameters):

  • Stream → Non-Stream Fallback (fallback_to_non_stream): If a stream ends prematurely leaving only thoughts and an EMPTY_ANSWER, it automatically retries with stream=False.
  • Thinking → Non-Thinking Retry (retry_without_thinking_when_empty): If the model gets stuck in an infinite thought loop and consumes all tokens without giving a final answer, it can automatically retry with enable_thinking=False.

Contributing

We welcome contributions! Please feel free to submit a Pull Request or open an Issue for bug reports and feature requests. Ensure all tests pass (pytest) before submitting a PR.

License

This project is licensed under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

llm_io_normalizer-0.1.1.tar.gz (20.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

llm_io_normalizer-0.1.1-py3-none-any.whl (16.1 kB view details)

Uploaded Python 3

File details

Details for the file llm_io_normalizer-0.1.1.tar.gz.

File metadata

  • Download URL: llm_io_normalizer-0.1.1.tar.gz
  • Upload date:
  • Size: 20.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for llm_io_normalizer-0.1.1.tar.gz
Algorithm Hash digest
SHA256 a9275a58d1e20d394cfffeecfeb9877d184d72fafd0217c4cac576e08d75e0f9
MD5 8845ba9b0f0b0ed7d7b1ca310b66e124
BLAKE2b-256 16a47f9a154240826131a129eb224a1f1b4b9c2bcf1f7c0c00172ecfc0516096

See more details on using hashes here.

Provenance

The following attestation bundles were made for llm_io_normalizer-0.1.1.tar.gz:

Publisher: publish.yml on wanghesong2019/llm-io-normalizer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file llm_io_normalizer-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for llm_io_normalizer-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d29a07c7dcb46a24e723b1ad4e5f08caa5fe53bb0251f47c60d3b90730bb04cc
MD5 f63aefbb2042576b8910ca24882e8507
BLAKE2b-256 483e5ad4d1363e2a2795c40897e4d3558125dc2d1b65fbcadd027c1f466147f1

See more details on using hashes here.

Provenance

The following attestation bundles were made for llm_io_normalizer-0.1.1-py3-none-any.whl:

Publisher: publish.yml on wanghesong2019/llm-io-normalizer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page