Skip to content

MessageBuilder

The MessageBuilder class simplifies the creation of messages with text, images, and file contents. This builder pattern makes it easy to construct complex messages with a fluent interface.

Usage Examples

Simple Image Analysis

This example shows how to analyze a single image using a multimodal LLM:

from mojentic.llm import LLMBroker
from mojentic.llm import MessageBuilder
from pathlib import Path

# Create an LLM broker with a multimodal model
llm = LLMBroker(model="gemma3:27b")

# Build a message with a single image
message = MessageBuilder("Please analyze this image and describe what you see:") \
    .add_image(Path.cwd() / "images" / "flash_rom.jpg") \
    .build()

# Generate a response
result = llm.generate(messages=[message])
print(result)

Comparing Two Images

This example demonstrates how to compare two images:

from mojentic.llm import LLMBroker
from mojentic.llm import MessageBuilder
from pathlib import Path

# Create an LLM broker
llm = LLMBroker(model="gemma3:27b")

# Build a message with two images for comparison
message = MessageBuilder("Compare these two images and tell me the differences:") \
    .add_image(Path.cwd() / "images" / "image1.jpg") \
    .add_image(Path.cwd() / "images" / "image2.jpg") \
    .build()

# Generate a response
result = llm.generate(messages=[message])
print(result)

Converting Java to Kotlin

This example shows how to use an LLM to convert a Java file to Kotlin:

from mojentic.llm import LLMBroker
from mojentic.llm import MessageBuilder
from pathlib import Path

# Create an LLM broker
llm = LLMBroker(model="gemma3:27b")

# Build a message with a Java file
message = MessageBuilder("Convert this Java code to Kotlin, maintaining the same functionality:") \
    .add_file(Path.cwd() / "src" / "example.java") \
    .build()

# Generate a response
result = llm.generate(messages=[message])
print(result)

Processing Multiple Markdown Documents

This example demonstrates how to summarize the contents of multiple markdown documents in a folder:

from mojentic.llm import LLMBroker
from mojentic.llm import MessageBuilder
from pathlib import Path

# Create an LLM broker
llm = LLMBroker(model="gemma3:27b")

# Build a message with multiple markdown files from a folder
message = MessageBuilder("Summarize the key points from these markdown documents:") \
    .add_files(Path.cwd() / "docs" / "*.md") \
    .build()

# Generate a response
result = llm.generate(messages=[message])
print(result)

Loading Prompt Content from a File

This example shows how to load a prompt from a file instead of hardcoding it in your code:

from mojentic.llm import LLMBroker
from mojentic.llm import MessageBuilder
from pathlib import Path

# Create an LLM broker
llm = LLMBroker(model="gemma3:27b")

# Load prompt content from a file
message = MessageBuilder() \
    .load_content(Path.cwd() / "prompts" / "code_review_prompt.txt") \
    .add_file(Path.cwd() / "src" / "main.py") \
    .build()

# Generate a response
result = llm.generate(messages=[message])
print(result)

Using Template Substitution in Prompts

This example demonstrates how to use template substitution when loading prompt content from a file:

from mojentic.llm import LLMBroker
from mojentic.llm import MessageBuilder
from pathlib import Path

# Create an LLM broker
llm = LLMBroker(model="gemma3:27b")

# Assume prompt_template.txt contains:
# "Please review this {language} code for {aspect} issues:"

# Load prompt content with template substitution
message = MessageBuilder() \
    .load_content(
        Path.cwd() / "prompts" / "prompt_template.txt",
        template_values={"language": "Python", "aspect": "security"}
    ) \
    .add_file(Path.cwd() / "src" / "main.py") \
    .build()

# The prompt will become: "Please review this Python code for security issues:"

# Generate a response
result = llm.generate(messages=[message])
print(result)

Using File References as Template Values

This example shows how to use file references as template values, where the content of the referenced file is used for substitution:

from mojentic.llm import LLMBroker
from mojentic.llm import MessageBuilder
from pathlib import Path

# Create an LLM broker
llm = LLMBroker(model="gemma3:27b")

# Assume prompt_template.txt contains:
# "Please analyze this code according to these {guidelines}:"

# Assume guidelines.txt contains detailed code review guidelines

# Load prompt content with file reference as template value
message = MessageBuilder() \
    .load_content(
        Path.cwd() / "prompts" / "prompt_template.txt",
        template_values={"guidelines": Path.cwd() / "prompts" / "guidelines.txt"}
    ) \
    .add_file(Path.cwd() / "src" / "main.py") \
    .build()

# The {guidelines} placeholder will be replaced with the entire content of guidelines.txt

# Generate a response
result = llm.generate(messages=[message])
print(result)

Important Considerations

LLM Context Size Limitations

When adding multiple files or large images to a message, be aware of LLM context size limitations:

  • Context Window Limits: Each LLM has a maximum context window size (e.g., 8K, 16K, 32K tokens).
  • Performance Impact: Adding too much data to messages will slow down the LLM's processing time.
  • Request Failures: Exceeding the context limit will cause the request to fail with an error.
  • Selective Content: Only include the most relevant files and images in your messages.
  • File Size: Large files may need to be split or summarized before processing.

For large datasets: - Consider chunking files into smaller segments - Process files sequentially rather than all at once - Use specialized document processing tools for very large documents

Key Features

  • Fluent Interface: Chain method calls for a clean, readable syntax
  • Multiple Content Types: Add text, images, and files to a single message
  • File Content Formatting: Automatically formats file contents with appropriate syntax highlighting
  • Type Detection: Detects file types and applies appropriate language tags for code blocks
  • Path Flexibility: Accepts both string paths and Path objects

Important Methods

  • add_image(): Add a single image to the message
  • add_images(): Add multiple images at once, with support for glob patterns
  • add_file(): Add a single file's content to the message
  • add_files(): Add multiple files at once, with support for glob patterns
  • load_content(): Load content from a file into the message, replacing any existing content, with optional template value substitution
  • build(): Create the final LLMMessage object to send to the LLM

API Reference

mojentic.llm.MessageBuilder

A builder class for creating LLM messages with text content, images, and files.

This class provides a fluent interface for constructing messages to be sent to an LLM, with support for adding text content, images, and file contents. It handles file reading, language detection for syntax highlighting, and proper formatting of the message.

Source code in src/mojentic/llm/message_composers.py
class MessageBuilder():
    """
    A builder class for creating LLM messages with text content, images, and files.

    This class provides a fluent interface for constructing messages to be sent to an LLM,
    with support for adding text content, images, and file contents. It handles file reading,
    language detection for syntax highlighting, and proper formatting of the message.
    """
    role: MessageRole
    content: Optional[str]
    image_paths: List[Path]
    file_paths: List[Path]
    type_sensor: FileTypeSensor

    def __init__(self, content: str = None):
        """
        Initialize a new MessageBuilder with optional text content.

        Parameters
        ----------
        content : str, optional
            Optional text content for the message. If None, the message will only
            contain added files and images.
        """
        self.role = MessageRole.User
        self.content = content
        self.image_paths = []
        self.file_paths = []
        self.type_sensor = FileTypeSensor()
        self.file_gateway = FileGateway()

    def _file_content_partial(self, file_path: Path) -> str:
        """
        Format the content of a file for inclusion in the message.

        This method reads the file content and formats it with appropriate markdown code-fence
        syntax highlighting based on the file extension.

        Parameters
        ----------
        file_path : Path
            Path to the file to be read and formatted

        Returns
        -------
        str
            Formatted string containing the file path and content with markdown code-fence
        """
        content = self.file_gateway.read(file_path)
        return (f"File: {file_path}\n"
                f"```{self.type_sensor.get_language(file_path)}\n"
                f"{content.strip()}\n"
                f"```\n")


    def add_image(self, image_path: Union[str, Path]) -> "MessageBuilder":
        """
        Add a single image to the message.

        Parameters
        ----------
        image_path : Union[str, Path]
            Path to the image file. Can be a string or Path object.

        Returns
        -------
        MessageBuilder
            The MessageBuilder instance for method chaining.

        Raises
        ------
        FileNotFoundError
            If the specified image file does not exist.
        """
        if isinstance(image_path, str):
            image_path = Path(image_path)
        if not self.file_gateway.exists(image_path):
            raise FileNotFoundError(f"Image file not found: {image_path}")
        if image_path not in self.image_paths:
            self.image_paths.append(image_path)
        return self

    def add_file(self, file_path: Union[str, Path]) -> "MessageBuilder":
        """
        Add a single file to the message.

        Parameters
        ----------
        file_path : Union[str, Path]
            Path to the file. Can be a string or Path object.

        Returns
        -------
        MessageBuilder
            The MessageBuilder instance for method chaining.

        Raises
        ------
        FileNotFoundError
            If the specified file does not exist.
        """
        if isinstance(file_path, str):
            file_path = Path(file_path)
        if not self.file_gateway.exists(file_path):
            raise FileNotFoundError(f"File not found: {file_path}")
        if file_path not in self.file_paths:
            self.file_paths.append(file_path)
        return self

    def add_images(self, *image_paths: Union[str, Path]) -> "MessageBuilder":
        """
        Add multiple images to the message.

        Parameters
        ----------
        *image_paths : Union[str, Path]
            Variable number of image paths. Can be strings or Path objects.
            Can include glob patterns like '*.jpg' to include all JPG and PNG images in a directory.

        Returns
        -------
        MessageBuilder
            The MessageBuilder instance for method chaining.
        """
        for path in image_paths:
            path_obj = Path(path) if isinstance(path, str) else path

            if path_obj.is_dir():
                for ext in ['*.jpg', '*.png']:
                    for img_path in path_obj.glob(ext):
                        self.add_image(img_path)
            elif '*' in str(path_obj):
                parent_dir = path_obj.parent if path_obj.parent != Path('.') else Path.cwd()
                for img_path in parent_dir.glob(path_obj.name):
                    if img_path.is_file():
                        self.add_image(img_path)
            else:
                self.add_image(path_obj)

        return self

    def add_files(self, *file_paths: List[Union[str, Path]]) -> "MessageBuilder":
        """
        Add multiple text files to the message, ignoring binary files.

        Parameters
        ----------
        *file_paths : Union[str, Path]
            Variable number of file paths. Can be strings or Path objects.
            Can include glob patterns like '*.txt' to include all text files in a directory.
            If a directory is provided, all text files in the directory will be added.

        Returns
        -------
        MessageBuilder
            The MessageBuilder instance for method chaining.
        """
        for path in file_paths:
            path_obj = Path(path) if isinstance(path, str) else path

            if path_obj.is_dir():
                # If a directory is provided, add all text files in the directory
                for file_path in path_obj.glob('*'):
                    if file_path.is_file() and not self.file_gateway.is_binary(file_path):
                        self.add_file(file_path)
            elif '*' in str(path_obj):
                # If a glob pattern is provided, add all matching text files
                parent_dir = path_obj.parent if path_obj.parent != Path('.') else Path.cwd()
                for file_path in parent_dir.glob(path_obj.name):
                    if file_path.is_file() and not self.file_gateway.is_binary(file_path):
                        self.add_file(file_path)
            else:
                # If a single file is provided, add it if it's a text file
                if path_obj.is_file() and not self.file_gateway.is_binary(path_obj):
                    self.add_file(path_obj)

        return self

    def load_content(self, file_path: Union[str, Path], template_values: Optional[Dict[str, Union[str, Path]]] = None) -> "MessageBuilder":
        """
        Load content from a file into the content field of the MessageBuilder.

        This method reads the content of the specified file and sets it as the content
        of the MessageBuilder, replacing any existing content. If template_values is provided,
        placeholders in the content will be replaced with the corresponding values.

        Parameters
        ----------
        file_path : Union[str, Path]
            Path to the file to load content from. Can be a string or Path object.
        template_values : Optional[Dict[str, Union[str, Path]]], optional
            Dictionary of values used to replace placeholders in the content.
            For example, if the content contains "{greeting}" and template_values is
            {"greeting": "Hello, World!"}, then "{greeting}" will be replaced with
            "Hello, World!".
            If a value is a Path object, it will be treated as a file reference and the
            content of that file will be used to replace the placeholder.
            Default is None.

        Returns
        -------
        MessageBuilder
            The MessageBuilder instance for method chaining.

        Raises
        ------
        FileNotFoundError
            If the specified file does not exist.
        """
        if isinstance(file_path, str):
            file_path = Path(file_path)
        if not self.file_gateway.exists(file_path):
            raise FileNotFoundError(f"File not found: {file_path}")
        self.content = self.file_gateway.read(file_path)

        # Replace placeholders with template values if provided
        if template_values:
            for key, value in template_values.items():
                if isinstance(value, Path):
                    # If value is a Path, read the content of the file
                    if not self.file_gateway.exists(value):
                        raise FileNotFoundError(f"Template file not found: {value}")
                    file_content = self.file_gateway.read(value).strip()
                    self.content = self.content.replace(f"{{{key}}}", file_content)
                else:
                    # If value is a string, use it directly
                    self.content = self.content.replace(f"{{{key}}}", value)

        return self

    def build(self) -> LLMMessage:
        """
        Build the final LLMMessage from the accumulated content, images, and files.

        This method combines all the content, file contents, and image paths into a single
        LLMMessage object that can be sent to an LLM. If files have been added, their contents
        will be formatted and included in the message content.

        Returns
        -------
        LLMMessage
            An LLMMessage object containing the message content and image paths.
        """
        parts = []
        if self.file_paths:
            file_contents = [self._file_content_partial(p) for p in self.file_paths]
            parts.append("\n\n".join(file_contents))
        if self.content is not None:
            parts.append(self.content)
        return LLMMessage(
            role=self.role,
            content="\n\n".join(parts),
            image_paths=[str(p) for p in self.image_paths]
        )

__init__(content=None)

Initialize a new MessageBuilder with optional text content.

Parameters:

Name Type Description Default
content str

Optional text content for the message. If None, the message will only contain added files and images.

None
Source code in src/mojentic/llm/message_composers.py
def __init__(self, content: str = None):
    """
    Initialize a new MessageBuilder with optional text content.

    Parameters
    ----------
    content : str, optional
        Optional text content for the message. If None, the message will only
        contain added files and images.
    """
    self.role = MessageRole.User
    self.content = content
    self.image_paths = []
    self.file_paths = []
    self.type_sensor = FileTypeSensor()
    self.file_gateway = FileGateway()

add_file(file_path)

Add a single file to the message.

Parameters:

Name Type Description Default
file_path Union[str, Path]

Path to the file. Can be a string or Path object.

required

Returns:

Type Description
MessageBuilder

The MessageBuilder instance for method chaining.

Raises:

Type Description
FileNotFoundError

If the specified file does not exist.

Source code in src/mojentic/llm/message_composers.py
def add_file(self, file_path: Union[str, Path]) -> "MessageBuilder":
    """
    Add a single file to the message.

    Parameters
    ----------
    file_path : Union[str, Path]
        Path to the file. Can be a string or Path object.

    Returns
    -------
    MessageBuilder
        The MessageBuilder instance for method chaining.

    Raises
    ------
    FileNotFoundError
        If the specified file does not exist.
    """
    if isinstance(file_path, str):
        file_path = Path(file_path)
    if not self.file_gateway.exists(file_path):
        raise FileNotFoundError(f"File not found: {file_path}")
    if file_path not in self.file_paths:
        self.file_paths.append(file_path)
    return self

add_files(*file_paths)

Add multiple text files to the message, ignoring binary files.

Parameters:

Name Type Description Default
*file_paths Union[str, Path]

Variable number of file paths. Can be strings or Path objects. Can include glob patterns like '*.txt' to include all text files in a directory. If a directory is provided, all text files in the directory will be added.

()

Returns:

Type Description
MessageBuilder

The MessageBuilder instance for method chaining.

Source code in src/mojentic/llm/message_composers.py
def add_files(self, *file_paths: List[Union[str, Path]]) -> "MessageBuilder":
    """
    Add multiple text files to the message, ignoring binary files.

    Parameters
    ----------
    *file_paths : Union[str, Path]
        Variable number of file paths. Can be strings or Path objects.
        Can include glob patterns like '*.txt' to include all text files in a directory.
        If a directory is provided, all text files in the directory will be added.

    Returns
    -------
    MessageBuilder
        The MessageBuilder instance for method chaining.
    """
    for path in file_paths:
        path_obj = Path(path) if isinstance(path, str) else path

        if path_obj.is_dir():
            # If a directory is provided, add all text files in the directory
            for file_path in path_obj.glob('*'):
                if file_path.is_file() and not self.file_gateway.is_binary(file_path):
                    self.add_file(file_path)
        elif '*' in str(path_obj):
            # If a glob pattern is provided, add all matching text files
            parent_dir = path_obj.parent if path_obj.parent != Path('.') else Path.cwd()
            for file_path in parent_dir.glob(path_obj.name):
                if file_path.is_file() and not self.file_gateway.is_binary(file_path):
                    self.add_file(file_path)
        else:
            # If a single file is provided, add it if it's a text file
            if path_obj.is_file() and not self.file_gateway.is_binary(path_obj):
                self.add_file(path_obj)

    return self

add_image(image_path)

Add a single image to the message.

Parameters:

Name Type Description Default
image_path Union[str, Path]

Path to the image file. Can be a string or Path object.

required

Returns:

Type Description
MessageBuilder

The MessageBuilder instance for method chaining.

Raises:

Type Description
FileNotFoundError

If the specified image file does not exist.

Source code in src/mojentic/llm/message_composers.py
def add_image(self, image_path: Union[str, Path]) -> "MessageBuilder":
    """
    Add a single image to the message.

    Parameters
    ----------
    image_path : Union[str, Path]
        Path to the image file. Can be a string or Path object.

    Returns
    -------
    MessageBuilder
        The MessageBuilder instance for method chaining.

    Raises
    ------
    FileNotFoundError
        If the specified image file does not exist.
    """
    if isinstance(image_path, str):
        image_path = Path(image_path)
    if not self.file_gateway.exists(image_path):
        raise FileNotFoundError(f"Image file not found: {image_path}")
    if image_path not in self.image_paths:
        self.image_paths.append(image_path)
    return self

add_images(*image_paths)

Add multiple images to the message.

Parameters:

Name Type Description Default
*image_paths Union[str, Path]

Variable number of image paths. Can be strings or Path objects. Can include glob patterns like '*.jpg' to include all JPG and PNG images in a directory.

()

Returns:

Type Description
MessageBuilder

The MessageBuilder instance for method chaining.

Source code in src/mojentic/llm/message_composers.py
def add_images(self, *image_paths: Union[str, Path]) -> "MessageBuilder":
    """
    Add multiple images to the message.

    Parameters
    ----------
    *image_paths : Union[str, Path]
        Variable number of image paths. Can be strings or Path objects.
        Can include glob patterns like '*.jpg' to include all JPG and PNG images in a directory.

    Returns
    -------
    MessageBuilder
        The MessageBuilder instance for method chaining.
    """
    for path in image_paths:
        path_obj = Path(path) if isinstance(path, str) else path

        if path_obj.is_dir():
            for ext in ['*.jpg', '*.png']:
                for img_path in path_obj.glob(ext):
                    self.add_image(img_path)
        elif '*' in str(path_obj):
            parent_dir = path_obj.parent if path_obj.parent != Path('.') else Path.cwd()
            for img_path in parent_dir.glob(path_obj.name):
                if img_path.is_file():
                    self.add_image(img_path)
        else:
            self.add_image(path_obj)

    return self

build()

Build the final LLMMessage from the accumulated content, images, and files.

This method combines all the content, file contents, and image paths into a single LLMMessage object that can be sent to an LLM. If files have been added, their contents will be formatted and included in the message content.

Returns:

Type Description
LLMMessage

An LLMMessage object containing the message content and image paths.

Source code in src/mojentic/llm/message_composers.py
def build(self) -> LLMMessage:
    """
    Build the final LLMMessage from the accumulated content, images, and files.

    This method combines all the content, file contents, and image paths into a single
    LLMMessage object that can be sent to an LLM. If files have been added, their contents
    will be formatted and included in the message content.

    Returns
    -------
    LLMMessage
        An LLMMessage object containing the message content and image paths.
    """
    parts = []
    if self.file_paths:
        file_contents = [self._file_content_partial(p) for p in self.file_paths]
        parts.append("\n\n".join(file_contents))
    if self.content is not None:
        parts.append(self.content)
    return LLMMessage(
        role=self.role,
        content="\n\n".join(parts),
        image_paths=[str(p) for p in self.image_paths]
    )

load_content(file_path, template_values=None)

Load content from a file into the content field of the MessageBuilder.

This method reads the content of the specified file and sets it as the content of the MessageBuilder, replacing any existing content. If template_values is provided, placeholders in the content will be replaced with the corresponding values.

Parameters:

Name Type Description Default
file_path Union[str, Path]

Path to the file to load content from. Can be a string or Path object.

required
template_values Optional[Dict[str, Union[str, Path]]]

Dictionary of values used to replace placeholders in the content. For example, if the content contains "{greeting}" and template_values is {"greeting": "Hello, World!"}, then "{greeting}" will be replaced with "Hello, World!". If a value is a Path object, it will be treated as a file reference and the content of that file will be used to replace the placeholder. Default is None.

None

Returns:

Type Description
MessageBuilder

The MessageBuilder instance for method chaining.

Raises:

Type Description
FileNotFoundError

If the specified file does not exist.

Source code in src/mojentic/llm/message_composers.py
def load_content(self, file_path: Union[str, Path], template_values: Optional[Dict[str, Union[str, Path]]] = None) -> "MessageBuilder":
    """
    Load content from a file into the content field of the MessageBuilder.

    This method reads the content of the specified file and sets it as the content
    of the MessageBuilder, replacing any existing content. If template_values is provided,
    placeholders in the content will be replaced with the corresponding values.

    Parameters
    ----------
    file_path : Union[str, Path]
        Path to the file to load content from. Can be a string or Path object.
    template_values : Optional[Dict[str, Union[str, Path]]], optional
        Dictionary of values used to replace placeholders in the content.
        For example, if the content contains "{greeting}" and template_values is
        {"greeting": "Hello, World!"}, then "{greeting}" will be replaced with
        "Hello, World!".
        If a value is a Path object, it will be treated as a file reference and the
        content of that file will be used to replace the placeholder.
        Default is None.

    Returns
    -------
    MessageBuilder
        The MessageBuilder instance for method chaining.

    Raises
    ------
    FileNotFoundError
        If the specified file does not exist.
    """
    if isinstance(file_path, str):
        file_path = Path(file_path)
    if not self.file_gateway.exists(file_path):
        raise FileNotFoundError(f"File not found: {file_path}")
    self.content = self.file_gateway.read(file_path)

    # Replace placeholders with template values if provided
    if template_values:
        for key, value in template_values.items():
            if isinstance(value, Path):
                # If value is a Path, read the content of the file
                if not self.file_gateway.exists(value):
                    raise FileNotFoundError(f"Template file not found: {value}")
                file_content = self.file_gateway.read(value).strip()
                self.content = self.content.replace(f"{{{key}}}", file_content)
            else:
                # If value is a string, use it directly
                self.content = self.content.replace(f"{{{key}}}", value)

    return self