hbllmutils.history.history

Conversation history utilities for Large Language Model (LLM) workflows.

This module provides a compact set of utilities for building and managing LLM-compatible message histories. It supports common content types such as plain text, PIL.Image.Image instances, and mixed content lists. Histories are stored in an immutable, sequence-like container that can be serialized to JSON or YAML for persistence and reuse.

The module contains the following public components:

Note

The history container is immutable. All modification methods return new instances, leaving the original history unchanged.

Example:

>>> from hbllmutils.history.history import LLMHistory, create_llm_message
>>> history = LLMHistory().with_user_message("Hello!")
>>> history = history.with_assistant_message("Hi there.")
>>> len(history)
2
>>> history[0]["role"]
'user'
>>> msg = create_llm_message(["Text", "More text"])
>>> msg["role"]
'user'

LLMContentTyping

hbllmutils.history.history.LLMContentTyping

alias of str | Image | List[str | Image]

LLMRoleTyping

hbllmutils.history.history.LLMRoleTyping

alias of Literal[‘system’, ‘user’, ‘assistant’, ‘tool’, ‘function’]

LLMHistory

class hbllmutils.history.history.LLMHistory(history: List[dict] | None = None)[source]

A sequence-like container for managing LLM conversation history.

This class provides methods to build and maintain a conversation history with different roles (user, assistant, system, etc.). It implements the collections.abc.Sequence protocol, allowing indexing and iteration. The class is designed to be immutable - all modification operations return new instances rather than modifying the existing one.

The class supports:

  • Adding messages with specific roles (user, assistant, system, etc.)

  • Setting and updating system prompts

  • Serialization to JSON and YAML formats

  • Deserialization from JSON and YAML files

  • Sequence operations (indexing, slicing, iteration, length)

  • Hashing and equality comparison

Note

LLMHistory is an immutable object. Any operation will cause a new object creation.

Parameters:

history (Optional[List[dict]]) – Optional initial history as a list of message dictionaries.

Example:

>>> history = LLMHistory()
>>> history = history.with_user_message("Hello!")
>>> history = history.with_assistant_message("Hi there!")
>>> len(history)
2
>>> history[0]
{'role': 'user', 'content': 'Hello!'}
__eq__(other: Any) bool[source]

Check equality between LLMHistory instances.

Two LLMHistory instances are considered equal if they have the same message history content. Returns False if the other object is not an LLMHistory instance.

Parameters:

other (LLMHistory) – Another LLMHistory instance to compare with.

Returns:

True if histories are equal, False otherwise.

Return type:

bool

Example:

>>> history1 = LLMHistory().with_user_message("Hello!")
>>> history2 = LLMHistory().with_user_message("Hello!")
>>> history1 == history2
True
__getitem__(index: int | slice) dict | LLMHistory[source]

Get an item or slice from the history.

When indexing with a single integer, returns a deep copy of the message dictionary at that position. When slicing, returns a new LLMHistory instance containing the sliced messages.

Parameters:

index (int or slice) – The index or slice to retrieve.

Returns:

A single message dict (deep copy) or a new LLMHistory instance for slices.

Return type:

dict or LLMHistory

Example:

>>> history = LLMHistory().with_user_message("Hello!")
>>> history[0]
{'role': 'user', 'content': 'Hello!'}
>>> history[0:1]
<LLMHistory object with 1 message>
__hash__() int[source]

Generate a hash value for the LLMHistory instance.

The hash is computed based on the message history content, allowing LLMHistory instances to be used as dictionary keys or in sets. The hash is computed by recursively converting nested data structures (dicts, lists) to hashable types (tuples).

Returns:

Hash value of the history.

Return type:

int

Example:

>>> history1 = LLMHistory().with_user_message("Hello!")
>>> history2 = LLMHistory().with_user_message("Hello!")
>>> hash(history1) == hash(history2)
True
>>> history_set = {history1, history2}
>>> len(history_set)
1
__init__(history: List[dict] | None = None) None[source]

Initialize the LLMHistory instance.

Parameters:

history (Optional[List[dict]]) – Optional initial history as a list of message dictionaries. Each dictionary should contain ‘role’ and ‘content’ keys.

__len__() int[source]

Get the number of messages in the history.

Returns:

The number of messages.

Return type:

int

Example:

>>> history = LLMHistory()
>>> len(history)
0
>>> history = history.with_user_message("Hello!")
>>> len(history)
1
clone() LLMHistory[source]

Create a deep copy of the current LLMHistory instance.

This method creates a new LLMHistory object with a deep copy of the internal message history, ensuring that modifications to the clone do not affect the original instance.

Returns:

A new LLMHistory instance with copied message history.

Return type:

LLMHistory

Example:

>>> history = LLMHistory()
>>> history = history.with_user_message("Hello!")
>>> cloned = history.clone()
>>> cloned = cloned.with_user_message("Another message")
>>> len(history)
1
>>> len(cloned)
2
dump_json(file: str, **params: Any) None[source]

Export the history to a JSON file.

Saves the conversation history to a JSON file with configurable formatting. The parent directory will be created if it doesn’t exist. Default parameters provide pretty-printed output with 2-space indentation, UTF-8 encoding, and sorted keys.

Parameters:
  • file (str) – The file path to save the JSON data.

  • params – Additional parameters to pass to json.dump() (e.g., indent, ensure_ascii). Default parameters: indent=2, ensure_ascii=False, sort_keys=True.

Raises:

IOError – If the file cannot be written.

Example:

>>> history = LLMHistory()
>>> history = history.with_user_message("Hello!")
>>> history.dump_json("conversation.json", indent=2)
dump_yaml(file: str, **params: Any) None[source]

Export the history to a YAML file.

Saves the conversation history to a YAML file with configurable formatting. The parent directory will be created if it doesn’t exist. Default parameters provide human-readable output with block style, UTF-8 encoding, 2-space indentation, and sorted keys.

Parameters:
  • file (str) – The file path to save the YAML data.

  • params – Additional parameters to pass to yaml.dump() (e.g., default_flow_style, indent). Default parameters: default_flow_style=False, allow_unicode=True, indent=2, sort_keys=True.

Raises:
  • IOError – If the file cannot be written.

  • ImportError – If PyYAML is not installed.

Example:

>>> history = LLMHistory()
>>> history = history.with_user_message("Hello!")
>>> history.dump_yaml("conversation.yaml", default_flow_style=False)
classmethod load_json(file: str) LLMHistory[source]

Load history from a JSON file.

Reads and validates a JSON file containing conversation history. The file must contain a list of message dictionaries, where each dictionary has role and content fields.

Parameters:

file (str) – The file path to load the JSON data from.

Returns:

A new LLMHistory instance loaded from the file.

Return type:

LLMHistory

Raises:
  • FileNotFoundError – If the file does not exist.

  • json.JSONDecodeError – If the file contains invalid JSON.

  • ValueError – If the JSON structure is invalid for LLMHistory.

Example:

>>> history = LLMHistory.load_json("conversation.json")
>>> len(history)
1
classmethod load_yaml(file: str) LLMHistory[source]

Load history from a YAML file.

Reads and validates a YAML file containing conversation history. The file must contain a list of message dictionaries, where each dictionary has role and content fields.

Parameters:

file (str) – The file path to load the YAML data from.

Returns:

A new LLMHistory instance loaded from the file.

Return type:

LLMHistory

Raises:
  • FileNotFoundError – If the file does not exist.

  • yaml.YAMLError – If the file contains invalid YAML.

  • ValueError – If the YAML structure is invalid for LLMHistory.

  • ImportError – If PyYAML is not installed.

Example:

>>> history = LLMHistory.load_yaml("conversation.yaml")
>>> len(history)
1
to_json() List[dict][source]

Convert the history to a JSON-serializable list of dictionaries.

Returns a deep copy of the internal message history, ensuring that modifications to the returned list do not affect the original history.

Returns:

A list of message dictionaries.

Return type:

List[dict]

Example:

>>> history = LLMHistory()
>>> history = history.with_user_message("Hello!")
>>> history.to_json()
[{'role': 'user', 'content': 'Hello!'}]
with_assistant_message(message: str | Image | List[str | Image]) LLMHistory[source]

Append an assistant message to the history.

This is a convenience method equivalent to calling LLMHistory.with_message() with role='assistant'. Creates a new LLMHistory instance with the appended message.

Parameters:

message (LLMContentTyping) – The message content.

Returns:

A new LLMHistory instance with the appended assistant message.

Return type:

LLMHistory

Example:

>>> history = LLMHistory()
>>> new_history = history.with_assistant_message("How can I help you?")
>>> new_history[0]["role"]
'assistant'
with_message(role: Literal['system', 'user', 'assistant', 'tool', 'function'], message: str | Image | List[str | Image]) LLMHistory[source]

Append a message with a specific role to the history.

This method creates a new LLMHistory instance with the appended message, leaving the original instance unchanged. The message content is processed through create_llm_message() to ensure proper formatting.

Parameters:
  • role (LLMRoleTyping) – The role of the message sender.

  • message (LLMContentTyping) – The message content.

Returns:

A new LLMHistory instance with the appended message.

Return type:

LLMHistory

Example:

>>> history = LLMHistory()
>>> new_history = history.with_message("user", "Hello!")
>>> len(history)
0
>>> len(new_history)
1
with_system_prompt(message: str | Image | List[str | Image]) LLMHistory[source]

Set or update the system prompt.

If a system message already exists at the beginning of the history, it will be replaced. Otherwise, the new system message will be inserted at the start of the history. This method creates a new LLMHistory instance.

System prompts are typically used to set the behavior or context for the LLM at the start of a conversation.

Parameters:

message (LLMContentTyping) – The system prompt content.

Returns:

A new LLMHistory instance with the system prompt set or updated.

Return type:

LLMHistory

Example:

>>> history = LLMHistory()
>>> new_history = history.with_system_prompt("You are a helpful assistant.")
>>> new_history[0]["role"]
'system'
>>> new_history[0]["content"]
'You are a helpful assistant.'
with_user_message(message: str | Image | List[str | Image]) LLMHistory[source]

Append a user message to the history.

This is a convenience method equivalent to calling LLMHistory.with_message() with role='user'. Creates a new LLMHistory instance with the appended message.

Parameters:

message (LLMContentTyping) – The message content.

Returns:

A new LLMHistory instance with the appended user message.

Return type:

LLMHistory

Example:

>>> history = LLMHistory()
>>> new_history = history.with_user_message("Hello!")
>>> new_history[0]["role"]
'user'

create_llm_message

hbllmutils.history.history.create_llm_message(message: str | Image | List[str | Image], role: Literal['system', 'user', 'assistant', 'tool', 'function'] = 'user') dict[source]

Create a structured LLM message from various content types.

This function converts different types of message content (text, images, or mixed) into a standardized dictionary format suitable for LLM APIs. The function handles:

  • Plain text strings: Returned as-is in the content field

  • PIL Images: Converted to blob URLs and wrapped in image_url format

  • Lists of mixed content: Each item is converted to appropriate format (text or image_url)

Parameters:
  • message (LLMContentTyping) – The message content, which can be a string, PIL Image, or list of strings/images.

  • role (LLMRoleTyping) – The role of the message sender (default is ‘user’).

Returns:

A dictionary containing the role and formatted content.

Return type:

dict

Raises:

TypeError – If the message type is unsupported or if a list item has an unsupported type.

Example:

>>> create_llm_message("Hello, world!")
{'role': 'user', 'content': 'Hello, world!'}

>>> # Mixed content: text plus an image instance
>>> create_llm_message(["Text message", Image.new("RGB", (10, 10))], role="assistant")
{'role': 'assistant', 'content': [{'type': 'text', 'text': 'Text message'}, {'type': 'image_url', 'image_url': '...'}]}