Skip to main content

A Python utility library for streamlined Large Language Model interactions with unified API and conversation management.

Project description

hbllmutils

PyPI PyPI - Python Version PyPI - Implementation PyPI - Downloads

Loc Comments Maintainability codecov

Code Test Badge Creation Package Release

GitHub stars GitHub forks GitHub commit activity GitHub issues GitHub pulls Contributors GitHub license

hbllmutils is a Python utility library designed to streamline interactions with Large Language Models (LLMs) by providing robust configuration management, a unified API for OpenAI-compatible endpoints, and intuitive conversation history handling.

Features

  • Flexible LLM Configuration: Easily manage multiple LLM API endpoints and models through a simple YAML configuration file (.llmconfig.yaml), supporting default and fallback settings.
  • OpenAI-Compatible API: Interact with various LLM providers that adhere to the OpenAI API specification, offering both synchronous and asynchronous request methods.
  • Streaming Responses: Efficiently handle streaming responses from LLMs, including optional extraction of reasoning content.
  • Conversation History Management: Build and maintain complex conversation histories with support for different roles (system, user, assistant) and multimodal content (text, images).
  • Structured Output with Auto-Prompting: Automatically generate detailed system prompts from Pydantic/dataclass models, including docstrings and comments, to ensure the LLM returns perfectly structured and validated JSON.
  • Automatic Retry for Parsing: Robustly handle malformed LLM output with an automatic retry mechanism until the response conforms to the required data model.
  • Extensible Design: Built with extensibility in mind, allowing for easy integration of new models or custom behaviors.

Installation

You can simply install it with the pip command line from the official PyPI site.

pip install hbllmutils

For more information about installation, you can refer to the Installation Guide.

Configuration: .llmconfig.yaml

The library uses a .llmconfig.yaml file to manage your LLM API credentials and model configurations. This file can be placed in your project's root directory or specified explicitly. Below is an example configuration demonstrating how to set up multiple API providers and define models, including default and fallback options.

deepseek: &deepseek
  base_url: https://api.deepseek.com/v1
  api_token: sk-457***af74

aihubmix: &aihubmix
  base_url: https://aihubmix.com/v1
  api_token: sk-6B9***F0Ad

aigcbest: &aigcbest
  base_url: https://api2.aigcbest.top/v1
  api_token: sk-tbK***49kA

openroute: &openroute
  base_url: https://openrouter.ai/api/v1
  api_token: sk-or-v1-9bf***a3d4

models:
  __default__:
    <<: *deepseek
    model_name: deepseek-chat

  deepseek-R1:
    <<: *deepseek
    model_name: deepseek-reasoner

  deepseek-V3:
    <<: *deepseek
    model_name: deepseek-chat

  __fallback__:
    <<: *aihubmix

Explanation of the configuration:

  • Anchors (& and *): YAML anchors are used to define reusable blocks. For example, &deepseek defines a block named deepseek which can be referenced later using *deepseek.
  • models section: This is the core of your model definitions.
    • __default__: Specifies the default model to use if no model_name is explicitly provided to load_llm_model.
    • deepseek-R1, deepseek-V3: Specific model configurations that inherit properties from the defined anchors and can override them (e.g., model_name).
    • __fallback__: Defines a fallback API endpoint. If a requested model_name is not found in the models section, the __fallback__ configuration will be used, with the requested model_name automatically assigned.

Quick Start Example

This example demonstrates how to load a model using the configuration file and interact with it using streaming responses and conversation history.

First, ensure you have a .llmconfig.yaml file set up as described above in your project directory.

import sys
from hbllmutils.history import LLMHistory
from hbllmutils.model import load_llm_model_from_config

# Load the LLM model named 'deepseek-V3' from your .llmconfig.yaml
model = load_llm_model_from_config(model_name='deepseek-V3')

# Initialize conversation history with a system prompt and a user message
history = LLMHistory().with_system_prompt(
    'You are a helpful assistant.'
).with_user_message(
    'Tell me a short, interesting fact about the ocean.'
)

# Ask the model a question and get a streaming response
f = model.ask_stream(messages=history.to_json())
print(f"\nStreaming Response:\n")

# Iterate through the stream and print chunks as they arrive
for chunk in f:
    print(chunk, end='')
    sys.stdout.flush()

print(f"\n\nAccumulated Content: {f.content}")

Structured Output with Data Models and Auto-Prompting

One of the most powerful features of hbllmutils is the ability to enforce structured output from LLMs using Python data models (like Pydantic's BaseModel or standard dataclass). The create_datamodel_task function automates the complex process of prompt engineering, validation, and retry logic.

Key Advantages:

  1. Auto-Prompt Generation: It uses a meta-LLM to read the source code of your data model class, including type hints, docstrings, and even field-level comments, to generate an extremely detailed and robust system prompt. This prompt guides the main LLM to produce perfectly formatted JSON.
  2. In-Context Learning (ICL): You can provide samples (input/output pairs) which are automatically formatted and injected into the system prompt, significantly improving the main LLM's adherence to the required structure and style.
  3. Automatic Retry: The underlying ParsableLLMTask automatically attempts to parse the LLM's JSON output and, if validation fails (e.g., malformed JSON, incorrect data types), it retries the request with an error message, guiding the LLM to correct its mistake.

Usage Example: create_datamodel_task

The following example demonstrates how to define a Person data model and use create_datamodel_task to reliably extract structured information from the LLM.

import logging
from pprint import pprint

from hbutils.logging import ColoredFormatter
from pydantic import BaseModel

from hbllmutils.model import load_llm_model_from_config
from hbllmutils.response import create_datamodel_task

# Set up colored logging (optional, but recommended)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
console_handler = logging.StreamHandler()
console_handler.setFormatter(ColoredFormatter())
logger.addHandler(console_handler)


class Person(BaseModel):
    gender: str  # male or female
    age: int
    hair_color: str  # use hex color
    skin_color: str  # use readable color
    appearance_desc: str  # a line of text for description of this guy


# Load your LLM model
model = load_llm_model_from_config(model_name='gpt-4o')
print(f"Loaded Model: {model}")

# 1. Define the task
task = create_datamodel_task(
    model=model,
    datamodel_class=Person,
    task_requirements="""
You are a bot to tell me the information of a celebrity.

I will give you his/her name, and you should tell me about his/her appearance information.
    """,
    samples=[
        # European female
        ("Taylor Swift", Person(
            gender="female",
            age=34,
            hair_color="#F5DEB3",  # blonde
            skin_color="fair",
            appearance_desc="Tall blonde singer with blue eyes, known for her elegant and graceful appearance"
        )),

        # African male
        ("Will Smith", Person(
            gender="male",
            age=55,
            hair_color="#2F1B14",  # dark brown
            skin_color="dark brown",
            appearance_desc="Charismatic actor with a bright smile, athletic build and confident demeanor"
        )),
    ]
)

# 2. Execute the task and automatically parse the result into a Person object
print(task.ask_then_parse('Jackie Chan'))
# Expected Output: gender='male' age=69 hair_color='#1C1C1C' skin_color='light brown' appearance_desc='Martial arts action star with a lively personality, known for his agile physique and distinctive smile'

print(task.ask_then_parse('Donald Trump'))
# Expected Output: gender='male' age=77 hair_color='#FFD700' skin_color='light' appearance_desc='Notable public figure known for his distinct hairstyle and fair complexion, often seen in formal suits'

print(task.ask_then_parse('Tohsaka Rin'))
# Expected Output: gender='female' age=17 hair_color='#2F1B14' skin_color='fair' appearance_desc='A young woman with twin-tailed brown hair and aqua eyes, usually seen wearing a red sweater and black skirt, exuding both elegance and a strong-willed demeanor'

The Auto-Prompting Mechanism

The magic happens in the background: create_datamodel_task uses a separate LLM (or the same one if prompt_generation_model is not specified) to analyze the Python source code of the Person class. It extracts the class definition, including the comments like # use hex color for hair_color, and uses this information to generate a highly specific system prompt that enforces all constraints.

The final system prompt sent to the main LLM will look something like this (simplified):

# Requirements

You are a bot to tell me the information of a celebrity.
I will give you his/her name, and you should tell me about his/her appearance information.

# Samples

... (Formatted samples here) ...

# Output guide

The output must be a single JSON object that strictly conforms to the following schema:

- **gender** (string): The gender of the person. Must be one of "male" or "female".
- **age** (integer): The age of the person.
- **hair_color** (string): The hair color. Must be a valid hex color code (e.g., #RRGGBB).
- **skin_color** (string): The skin color. Must be a readable color name (e.g., "fair", "dark brown").
- **appearance_desc** (string): A single line of text for the description of this guy.

This two-stage process (meta-LLM for prompt generation, main LLM for task execution) ensures maximum reliability and structure adherence.

Advanced Features

FakeLLMModel for Testing and Development

hbllmutils provides a FakeLLMModel that allows developers to simulate LLM behavior for testing, debugging, and rapid prototyping without incurring API costs or waiting for real API responses. This model can be configured with predefined rules to return specific responses based on input messages, supporting both synchronous and streaming interactions.

Key Features of FakeLLMModel:

  • Configurable Responses: Define rules to return specific text or (reasoning, content) tuples.
  • Conditional Logic: Set up responses based on conditions like keywords in the last message or custom functions.
  • Streaming Simulation: Simulate streaming responses with a customizable words-per-second rate.
  • Method Chaining: Rules can be added using a fluent API.

Usage Examples:

from hbllmutils.model import FakeLLMModel
from hbllmutils.history import LLMHistory
import sys

# Initialize FakeLLMModel with a streaming speed of 10 words per second
model = FakeLLMModel(stream_wps=10)

# 1. Always return a specific response
model.response_always("Hello, I am a fake LLM model ready for your commands!")
history_always = LLMHistory().with_user_message("Hi there!")
response_always = model.ask(history_always.to_json())
print(f"Always Response: {response_always}")
# Expected Output: Always Response: Hello, I am a fake LLM model ready for your commands!

# 2. Respond based on a keyword in the last message
model = FakeLLMModel(stream_wps=10)  # Re-initialize to clear previous rules
model.response_when_keyword_in_last_message("weather", "The weather is sunny with a chance of fake clouds.")
model.response_when_keyword_in_last_message(["time", "hour"], "It\'s always coffee o\'clock in the fake world.")

history_weather = LLMHistory().with_user_message("What\'s the weather like?")
response_weather = model.ask(history_weather.to_json())
print(f"Weather Response: {response_weather}")
# Expected Output: Weather Response: The weather is sunny with a chance of fake clouds.

history_time = LLMHistory().with_user_message("What time is it?")
response_time = model.ask(history_time.to_json())
print(f"Time Response: {response_time}")
# Expected Output: Time Response: It\'s always coffee o\'clock in the fake world.

# 3. Respond based on a custom condition
model = FakeLLMModel(stream_wps=10)  # Re-initialize to clear previous rules


def long_conversation_check(messages, **params):
    return len(messages) > 2


model.response_when(long_conversation_check, "This is a long conversation, isn\'t it?")
model.response_always("Short conversation.")  # Fallback for shorter conversations

history_short = LLMHistory().with_user_message("Hello.")
response_short = model.ask(history_short.to_json())
print(f"Short Conversation: {response_short}")
# Expected Output: Short Conversation: Short conversation.

history_long = LLMHistory().with_user_message("Hello.").with_assistant_message("Hi!").with_user_message("How are you?")
response_long = model.ask(history_long.to_json())
print(f"Long Conversation: {response_long}")
# Expected Output: Long Conversation: This is a long conversation, isn\'t it?

# 4. Streaming responses with reasoning
model = FakeLLMModel(stream_wps=5)  # Slower streaming for demonstration
model.response_always(("Thinking step by step...", "The final answer is 42."))

history_stream = LLMHistory().with_user_message("What is the meaning of life?")
stream = model.ask_stream(history_stream.to_json(), with_reasoning=True)

print("\nStreaming Response (with reasoning):\n")
for chunk in stream:
    print(chunk, end='')
    sys.stdout.flush()

print(f"\n\nAccumulated Reasoning: {stream.reasoning_content}")
print(f"Accumulated Content: {stream.content}")
# Expected Output (with simulated delay):
# Streaming Response (with reasoning):
# Thinking step by step...The final answer is 42.
#
# Accumulated Reasoning: Thinking step by step...
# Accumulated Content: The final answer is 42.

LLM Liveness and Readiness Probes: hello and ping

hbllmutils.testing.alive module provides simple, yet effective, binary tests to check the liveness and readiness of your LLM models. These functions are particularly useful for ensuring that your integrated LLM services are operational and responding as expected.

hello Function

The hello function sends a basic greeting to the LLM and checks if it receives any response. It's a fundamental liveness probe to confirm that the model is accessible and capable of generating output.

Usage Example:

from hbllmutils.model import FakeLLMModel
from hbllmutils.testing.alive import hello

# Create a fake model for demonstration
model = FakeLLMModel()
model.response_always("Hello! How can I assist you today?")

# Perform a single hello test
hello_result = hello(model)
print(f"Hello Test Passed: {hello_result.passed}")
print(f"Hello Test Content: {hello_result.content}")
# Expected Output:
# Hello Test Passed: True
# Hello Test Content: Hello! How can I assist you today?

# Perform multiple hello tests
multi_hello_results = hello(model, n=5)
print(f"Multi Hello Tests Passed Count: {multi_hello_results.passed_count}")
print(f"Multi Hello Tests Passed Ratio: {multi_hello_results.passed_ratio}")
# Expected Output:
# Multi Hello Tests Passed Count: 5
# Multi Hello Tests Passed Ratio: 1.0

ping Function

The ping function sends a "ping!" message to the LLM and expects a response containing "pong" (case-insensitive). This serves as a readiness probe, verifying that the model can process specific input and generate a predictable response, indicating its readiness for more complex tasks.

Usage Example:

from hbllmutils.model import FakeLLMModel
from hbllmutils.testing.alive import ping

# Create a fake model for demonstration
model = FakeLLMModel()
model.response_when_keyword_in_last_message("ping!", "Pong! I am ready.")

# Perform a single ping test
ping_result = ping(model)
print(f"Ping Test Passed: {ping_result.passed}")
print(f"Ping Test Content: {ping_result.content}")
# Expected Output:
# Ping Test Passed: True
# Ping Test Content: Pong! I am ready.

# Perform multiple ping tests
multi_ping_results = ping(model, n=3)
print(f"Multi Ping Tests Passed Count: {multi_ping_results.passed_count}")
print(f"Multi Ping Tests Passed Ratio: {multi_ping_results.passed_ratio}")
# Expected Output:
# Multi Ping Tests Passed Count: 3
# Multi Ping Tests Passed Ratio: 1.0

LLM-Assisted Python Code Generation (hbllmutils.meta.code)

hbllmutils provides a powerful set of utilities under hbllmutils.meta.code for LLM-assisted Python code analysis and generation. These tools are designed to streamline common development tasks by leveraging LLMs to generate documentation, complete TODOs, and create unit tests, all while ensuring Pythonic correctness and adherence to best practices.

Key Features:

  • Comprehensive Source Code Analysis: Detailed analysis of Python source files, including package namespace, dependencies, and module directory structure.
  • AST-based Validation: Automatically validates generated code for syntactic correctness using Python's Abstract Syntax Tree (AST).
  • Configurable LLM Tasks: Flexible task creation with options for model selection, retry mechanisms, and context provision.

1. Pydoc Generation

The create_pydoc_generation_task function enables the automatic generation of comprehensive Python documentation ( pydoc) in reStructuredText format. This includes module-level docstrings, class and function documentation, parameter descriptions with type annotations, return values, exceptions, and usage examples.

Pythonic Example:

import os
from hbllmutils.model import load_llm_model
from hbllmutils.meta.code.pydoc_generation import create_pydoc_generation_task

# Create a dummy Python file for demonstration
with open('my_module.py', 'w') as f:
    f.write("""
\"\"\"A simple module for arithmetic operations.\"\"\"

def add(a: int, b: int) -> int:
    # TODO: Implement addition
    return a + b

class Calculator:
    \"\"\"A simple calculator class.\"\"\"
    def multiply(self, x: int, y: int) -> int:
        \"\"\"Multiplies two integers.\"\"\"
        return x * y
"""
            )

# Load your LLM model (e.g., 'gpt-4')
# Ensure you have your .llmconfig.yaml configured or OPENAI_API_KEY set
model = load_llm_model('gpt-4')

# Create a pydoc generation task
task = create_pydoc_generation_task(model, show_module_directory_tree=True)

# Generate documentation for the Python file
documented_code = task.ask_then_parse(input_content='my_module.py')

print("\n--- Documented Code ---\n")
print(documented_code)

# Optionally, save the documented code back to the file
with open('my_module.py', 'w') as f:
    f.write(documented_code)

# Clean up dummy file
os.remove('my_module.py')

2. TODO Completion

The create_todo_completion_task function facilitates the automatic completion of TODO comments within Python source code. It analyzes the code context around TODOs and uses an LLM to generate appropriate code snippets, ensuring consistency and adherence to existing code patterns.

Pythonic Example:

import os
from hbllmutils.model import load_llm_model
from hbllmutils.meta.code.todo_completion import create_todo_completion_task

# Create a dummy Python file with TODOs
with open('tasks.py', 'w') as f:
    f.write("""
def greet(name: str):
    # TODO: Add a personalized greeting message
    pass

def calculate_area(length: float, width: float) -> float:
    # TODO: Calculate the area of a rectangle
    # The formula is length * width
    pass
"""
            )

# Load your LLM model
model = load_llm_model('gpt-4')

# Create a TODO completion task
task = create_todo_completion_task(model, show_module_directory_tree=False)

# Complete TODOs in the file
completed_code = task.ask_then_parse(input_content='tasks.py')

print("\n--- Completed Code ---\n")
print(completed_code)

# Optionally, save the completed code back to the file
with open('tasks.py', 'w') as f:
    f.write(completed_code)

# Clean up dummy file
os.remove('tasks.py')

3. Unit Test Generation

The create_unittest_generation_task function enables the automatic generation of unit tests for Python source files. It supports various test frameworks (e.g., pytest, unittest) and can use existing test files as a reference to maintain style and patterns. The generated tests cover the functionality of the source code, including edge cases.

Pythonic Example:

import os
from hbllmutils.model import load_llm_model
from hbllmutils.meta.code.unittest_generation import create_unittest_generation_task

# Create a dummy Python file for which to generate tests
with open('math_operations.py', 'w') as f:
    f.write("""
def factorial(n: int) -> int:
    \"\"\"Calculate the factorial of a non-negative integer.\"\"\"
    if n < 0:
        raise ValueError("Factorial is not defined for negative numbers")
    if n == 0:
        return 1
    res = 1
    for i in range(1, n + 1):
        res *= i
    return res

def is_prime(num: int) -> bool:
    \"\"\"Check if a number is prime.\"\"\"
    if num < 2:
        return False
    for i in range(2, int(num**0.5) + 1):
        if num % i == 0:
            return False
    return True
"""
            )

# Load your LLM model
model = load_llm_model('gpt-4')

# Create a unit test generation task for pytest
task = create_unittest_generation_task(
    model=model,
    test_framework_name='pytest',
    mark_name='unittest'
)

# Generate tests for the math_operations.py file
generated_tests = task.generate(source_file='math_operations.py')

print("\n--- Generated Unit Tests ---\n")
print(generated_tests)

# Optionally, save the generated tests to a test file
with open('test_math_operations.py', 'w') as f:
    f.write(generated_tests)

# Clean up dummy files
os.remove('math_operations.py')
os.remove('test_math_operations.py')

Command-Line Interface (CLI) (hbllmutils)

The hbllmutils package provides a convenient command-line interface (CLI) to access its powerful LLM-assisted code generation and analysis features directly from your terminal. This allows for quick integration into development workflows, scripting, and automation.

Installation for CLI Usage

To use the hbllmutils CLI, ensure the package is installed:

pip install hbllmutils

General Usage

The main entry point for the CLI is hbllmutils. The code generation functionalities are grouped under the code subcommand.

hbllmutils --help
hbllmutils code --help

1. Pydoc Generation CLI

Generate Python documentation for a single file or an entire directory using the hbllmutils code pydoc command. This command leverages the same underlying LLM tasks as the Pythonic API to produce high-quality reStructuredText docstrings.

CLI Examples:

  • Generate documentation for a single file:

    hbllmutils code pydoc -i my_module.py -m gpt-4
    
  • Generate documentation for all Python files in a directory:

    hbllmutils code pydoc -i my_package/ -m gpt-4 --timeout 300
    
  • Specify additional model parameters (e.g., max_tokens, temperature):

    hbllmutils code pydoc -i my_file.py -m gpt-4 --param max_tokens=128000 --param temperature=0.7
    
  • Ignore specific modules during dependency analysis:

    hbllmutils code pydoc -i my_file.py -m gpt-4 --ignore-modules numpy pandas
    

2. TODO Completion CLI

Automatically complete TODO comments in Python source files using the hbllmutils code todo command. This command intelligently fills in missing code based on the context of the TODO comment and surrounding code.

CLI Examples:

  • Complete TODOs in a single file:

    hbllmutils code todo -i tasks.py -m gpt-4
    
  • Complete TODOs in all Python files within a directory:

    hbllmutils code todo -i my_project/src/ -m gpt-4 --timeout 300
    
  • Process non-Python code files (e.g., JavaScript) with TODOs:

    hbllmutils code todo -i app.js -m gpt-4 --no-python-code
    

3. Unit Test Generation CLI

Generate unit tests for your Python code using the hbllmutils code unittest command. You can specify the test framework and even provide an existing test file as a reference for style.

CLI Examples:

  • Generate pytest unit tests for a source file:

    hbllmutils code unittest -s math_operations.py -o test_math_operations.py -m gpt-4 --framework pytest --mark unittest
    
  • Generate unittest-style tests and use an existing test file as reference:

    hbllmutils code unittest -s my_service.py -o test_my_service.py -t existing_tests/test_service_base.py -m gpt-4 --framework unittest
    
  • Generate tests for all Python files in a directory:

    hbllmutils code unittest -s my_app/logic/ -o tests/logic/ -m gpt-4 --framework pytest
    

These CLI tools provide a flexible and efficient way to integrate LLM-powered code generation into your development workflow, enhancing productivity and code quality.

Contributing

Contributions are welcome! Please feel free to open issues or submit pull requests on the GitHub repository.

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

hbllmutils-0.4.3.tar.gz (332.6 kB view details)

Uploaded Source

Built Distribution

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

hbllmutils-0.4.3-py3-none-any.whl (361.5 kB view details)

Uploaded Python 3

File details

Details for the file hbllmutils-0.4.3.tar.gz.

File metadata

  • Download URL: hbllmutils-0.4.3.tar.gz
  • Upload date:
  • Size: 332.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for hbllmutils-0.4.3.tar.gz
Algorithm Hash digest
SHA256 ecc3907224d8c0c8fe348bc583e417c6b502a2cd062162e8503b2ea2535a9dc8
MD5 452ce90861868939868cfc80e8271353
BLAKE2b-256 3489c004311ea3fb798ec42f3ba31278752add7e742b28da52eb935b28eeb886

See more details on using hashes here.

File details

Details for the file hbllmutils-0.4.3-py3-none-any.whl.

File metadata

  • Download URL: hbllmutils-0.4.3-py3-none-any.whl
  • Upload date:
  • Size: 361.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for hbllmutils-0.4.3-py3-none-any.whl
Algorithm Hash digest
SHA256 85c79896cc38f19ad023795c3ea448d18b37ecc6efb8baa57439157402f0aa44
MD5 c6bf4217488f56b9031543055e3f1660
BLAKE2b-256 a9319aae946dfca7519e6ca72d638eee928756ea3dfc6ed4efdc73f17678b7f4

See more details on using hashes here.

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