ChatWeaver – A Comprehensive Overview
ChatWeaver is a Python library designed to simplify and enhance chatbot development with OpenAI. By offering intuitive abstractions and structures—such as the Model
, Bot
, Chat
, TextNode
, and CWArchive
classes—ChatWeaver consolidates much of the boilerplate and complexity associated with multi-turn conversations, file integrations, and persistent storage.
Why ChatWeaver?
- Abstraction Layer: It wraps the OpenAI API in high-level classes, reducing the need to manage low-level prompts and token usage manually.
- Conversation Management: Built-in classes handle chat history, user messages, file attachments, and more.
- Persistence: You can easily save and retrieve entire
Chat
sessions or multiple objects with CWArchive
.
- Scalability: Configuration of models, tokens, and asynchronous loading helps you tailor ChatWeaver to your production environment.
Core Components
1. Model
A wrapper for OpenAI-based models, ensuring valid API keys and model names.
2. Bot
Extends Model
to create a customizable AI assistant with definable rules and a unique name.
3. TextNode
A lightweight, immutable container for individual messages in a conversation.
-
Example:
from chatweaver import TextNode
user_node = TextNode(
role="user",
content="Hello, what's the weather like?",
owner="Alice",
tokens=8,
date="2025-01-01 09:15:00"
)
-
Key Capabilities:
- Stores message details like role (assistant/user), content, tokens used, and timestamp.
- Easily converted to a dictionary or a string for logging or serialization.
4. Chat
Builds upon the Bot
to manage an entire conversation session, storing user/assistant exchanges (TextNode
objects), reply limits, and session-level metadata like title or creation date.
-
Initialization:
from chatweaver import Chat
chat_session = Chat(
api_key="sk-1234567890abcdef1234...",
title="Support Session",
replies_limit=5,
user="Customer"
)
-
Key Capabilities:
-
Useful Properties:
chat_replies
– counts how many user+assistant pairs have occurred.
chat_cost
– sums the tokens used across all messages.
chat_title
– a helpful descriptor for logs or archives.
5. CWArchive
Handles persistent storage of any combination of Chat
, Bot
, or Model
objects. You can load, save, and manage them by their integer IDs or by direct reference to the objects.
-
Initialization:
from chatweaver import CWArchive
archive = CWArchive(
path="my_chats.archive"
)
-
Key Capabilities:
- Add/Remove: Insert or delete items (by ID or by object).
- Retrieve: Load all items from disk, either sequentially or concurrently. (better using the archive_data attribute instead)
- Save: Write the current in-memory items back to the file, ensuring data consistency.
-
Usage:
chat_session = Chat(api_key="sk-1234567890abcdef1234...", title="My Chat Session")
archive.add(chat_session)
archive.save()
data = archive.archive_data
for item_id, chat_obj in data.items():
print(item_id, chat_obj)
archive.remove(0)
archive.remove(chat_session)
Putting It All Together – Example Workflow
Below is a high-level example illustrating how you might use ChatWeaver end-to-end:
from chatweaver import Model, Bot, Chat, CWArchive
model = Model(api_key="sk-1234567890abcdef1234", model="gpt-4o")
rules = "You are a friendly and knowledgeable assistant."
bot = Bot(
cw_model=model,
rules=rules,
name="HelpfulBot"
)
chat = Chat(
cw_bot=bot,
title="Demo Session",
replies_limit=3,
user="Tester"
)
response1 = chat.get_response("Hello, how are you?")
print("Bot:", response1)
response2 = chat.get_response("Can you tell me a joke?")
print("Bot:", response2)
print("Current replies used:", chat.chat_replies)
print("Total tokens so far:", chat.chat_cost)
archive = CWArchive(path="demo_archive.txt", asynchronous=True, delay=0.07)
archive.add(chat)
archive.save()
loaded_data = archive.archive_data
for item_id, obj in loaded_data.items():
print(f"Item ID {item_id} -> {obj}")
In this script:
- Model: We create or reference an OpenAI model with a valid API key.
- Bot: We attach conversation rules and specify a bot name, letting it use our
Model
.
- Chat: We start a conversation session, linking it to our
Bot
. We optionally limit how many user+assistant pairs are allowed.
- Conversation: We ask the bot questions and observe the replies.
- Persistence: Finally, we save the entire
Chat
to an archive file and later re-load it to continue the session.
Final Thoughts
ChatWeaver aims to reduce the friction in creating robust, multi-turn chatbot experiences powered by OpenAI. Whether you need to store your sessions locally, manage advanced conversation rules, or simply test a new idea in minutes, ChatWeaver’s structured approach and flexible design provide a powerful toolkit for modern chatbot development.
ChatWeaver – A Detailed Overview
Model
The Model class provides a straightforward way to configure and manage OpenAI-based API clients. It validates and stores both the model name and the API key, automatically initializing a matching OpenAI client for you.
Description
- Purpose: Encapsulate the details of model configuration (e.g., the model name, API key, and associated client) to simplify usage within your application.
- Validation: Ensures both the API key and model name meet the required formats and are tested before actual usage.
- Automatic Client Creation: Whenever you update the API key, a new OpenAI client is initialized if validation succeeds.
Attributes
-
model_api_key: str
Retrieve or set the API key associated with this Model. Must begin with "sk-"
and be at least 20 characters long.
-
model_model: str
Retrieve or set the model name. The default value is "gpt-4o"
. Changing this property triggers internal validation against an accepted list of models.
-
model_client: openai.OpenAI
Retrieve the current OpenAI client. When the API key changes, a new client is automatically created.
Methods
Below is an overview of the principal methods available on the Model class.
-
Constructor
__init__(api_key: str, model: str = "gpt-4o")
Initializes the Model with a valid API key and an optional model name.
- Parameters:
api_key (str)
: Must begin with "sk-"
and be at least 20 characters.
model (str)
: The model name to use, defaulting to "gpt-4o"
.
- Raises:
Exception
: If either the model name or the API key fails validation.
-
__str__() -> str
Returns a user-friendly string containing the current model name and a partially obfuscated API key.
-
__repr__() -> str
Returns a more detailed string representation useful for debugging or logging.
-
__eq__(other) -> bool
Determines equality between two Model instances by comparing both their model names and API keys.
-
model_model (getter)
Retrieves the currently set model name.
-
model_model (setter)
Validates and sets a new model name. Raises an Exception
if the new name is not in the accepted list of models.
-
model_api_key (getter)
Retrieves the currently stored API key.
-
model_api_key (setter)
Validates the format of the new API key (must start with "sk-"
and be at least 20 characters). Performs a test request to ensure the key is valid. If successful, updates the API key and re-initializes the client.
-
model_client (getter)
Retrieves the OpenAI client currently in use.
-
model_client (setter)
Explicitly sets a new OpenAI client by providing the same API key already stored in model_api_key
. If the provided key does not match the stored one, an exception is raised.
Usage Example
from chatweaver import Model
model_instance = Model(api_key="sk-1234567890abcdef1234", model="gpt-4o")
print(str(model_instance))
model_instance.model_model = "gpt-4o"
model_instance.model_api_key = "sk-0987654321abcdef5678"
client = model_instance.model_client
another_model = Model(api_key="sk-0987654321abcdef5678", model="gpt-4o")
print(model_instance == another_model)
Use the Model class to simplify how you manage different OpenAI models and keys across your chatbot applications.
Bot
The Bot class extends the capabilities of the Model
class to create a conversational AI entity. It allows you to specify custom rules, assign a name to the bot, and configure time formats. You can also use it to generate responses to user queries, optionally integrating images or file uploads.
Description
- Purpose: Provide a flexible chatbot built on a specific
Model
.
- Customization: Lets you define conversation rules, set a bot name, and attach images or files to user prompts.
- Integration: If you already have a
Model
instance, you can pass it in; otherwise, the Bot
will initialize its own Model
by inheriting from Model
.
Attributes
-
bot_rules: str
The conversation rules that guide the bot’s responses. These rules can be overridden if needed. The getter automatically appends the bot’s name to provide context in dialogues.
-
bot_name: str
The human-readable name assigned to the bot (default is "AI Bot"
). This name also appears in the conversation rules.
-
bot_time_format: str
The format string used for handling timestamps in the bot’s operation (default is "%d/%m/%Y %H:%M:%S"
).
-
bot_Model: Model
The underlying Model
instance that interfaces with OpenAI. Access this property when you need direct control over the model settings or the OpenAI client.
Methods
Below is a summary of the key methods in the Bot class.
Constructor
__init__( *args, rules: str | None = ..., name: str = "AI Bot", cw_model: Model | None = None, **kwargs ) -> None
Initializes the Bot with optional rules, a custom name, and an existing Model
(if available). If no Model
is provided, the bot internally creates one by calling super().__init__
.
-
Parameters:
*args
: Additional positional arguments passed to the parent Model
if cw_model
is not given.
rules (str | None)
: Conversation rules for the bot. Falls back to default if None
.
name (str)
: The bot’s name (default is "AI Bot"
).
cw_model (Model | None)
: An existing Model
instance. If None
, a new Model
is created.
**kwargs
: Additional keyword arguments passed to the parent Model
if cw_model
is not given.
-
Raises:
TypeError
: If cw_model
is provided but is not a Model
instance.
__str__() -> str
Returns a friendly string with the bot’s name, for quick identification.
__repr__() -> str
Returns a more detailed string representation, useful for debugging (includes name, rules, and the underlying Model
).
__eq__(other: Bot) -> bool
Compares this bot to another Bot instance. Returns True
if both have the same rules, name, and underlying Model
.
bot_Model (getter)
Retrieves the underlying Model
used by this bot. This allows you to work directly with the Model
’s methods and properties if needed.
bot_rules (getter/setter)
- Getter: Returns the bot’s current rules (automatically appending the bot’s name).
- Setter: Updates the bot’s rules. If
None
, defaults to a predefined rule set.
bot_name (getter/setter)
- Getter: Returns the bot’s current name.
- Setter: Assigns a new name to the bot, which also appears in its rules.
bot_time_format (getter/setter)
- Getter: Returns the bot’s time format string.
- Setter: Assigns a new time format. Raises a
ValueError
if the format is invalid.
Generating Responses
Use the response
method to get a chatbot response from a user prompt:
response(prompt: str, user: str = "User", history: list | None = None, image_path: str | None = None, file_path: str | None = None) -> dict[str, Any]
Constructs a message set (including optional conversation history) and sends it to the underlying Model
’s OpenAI client. If image_path
or file_path
is provided, these files are appropriately handled (e.g., base64-encoded images, file uploads) and referenced in the prompt.
Usage Example
from chatweaver import Bot, Model
existing_model = Model(api_key="sk-1234567890abcdef1234...", model="gpt-4o")
bot = Bot(
cw_model=existing_model,
rules="You are a helpful assistant with a witty sense of humor.",
name="FriendlyBot"
)
another_bot = Bot(
api_key="sk-0987654321abcdef5678...",
rules="You are a formal assistant that provides concise answers.",
name="ConciseBot"
)
response_data = bot.response(
prompt="What is the capital of France?",
user="Alice",
image_path=None,
file_path=None
)
print("Bot response:", response_data["content"])
print("Token usage:", response_data["total_tokens"])
In this example, you can see how you might create Bot
instances—either using an existing Model
or letting Bot
handle model creation. You can then generate answers to user prompts, optionally providing conversation history and file attachments.
TextNode
The TextNode class is a simple, immutable data container representing a single message in a conversation. Each instance stores key information about the message, including its role (assistant or user), the message content, the name of the owner, token usage, and creation date.
Description
- Immutability:
TextNode
is defined with @dataclass(frozen=True)
, meaning its fields cannot be modified after creation.
- Conversation Element: Each
TextNode
encapsulates one message. In a broader chatbot flow, multiple TextNode
instances together form the conversation history.
Attributes
-
role: str
The role associated with this text node, typically "assistant"
or "user"
.
-
content: str
The actual text content of the message.
-
owner: str
The name or identifier of the owner of this node (e.g., "AI Bot"
, "User"
, etc.).
-
tokens: int
The number of tokens estimated or measured for this message content. Useful for tracking token usage in OpenAI-based systems.
-
date: str
A string representation of the date or timestamp at which this message was created.
Methods
-
__str__() -> str
Returns a JSON-like string of all attributes in the TextNode
. For example:
{
"role": "assistant",
"content": "Hello!",
"owner": "AI Bot",
"tokens": 5,
"date": "2024-12-31 10:00:00"
}
-
__iter__() -> dict
Provides an iterator over the TextNode
’s attributes as key-value pairs. This allows you to quickly convert a TextNode
to a dictionary by calling dict(text_node_instance)
.
Usage Example
Below is a simple demonstration of how you might create a TextNode
and then inspect its attributes.
from chatweaver import TextNode
user_message = TextNode(
role="user",
content="What's the weather like today?",
owner="Alice",
tokens=8,
date="2025-01-01 09:15:00"
)
print(str(user_message))
node_dict = dict(user_message)
print(node_dict["content"])
Use TextNode
objects to store and pass around conversation messages reliably, ensuring their metadata remains intact across different parts of your chatbot or application.
Chat
The Chat class extends the functionality of the Bot
class to manage a full conversation session. It retains a history of messages (via TextNode
objects), handles reply limits, and tracks metadata such as the chat title, creation date, and total cost (in tokens). This class is ideal for organizing multi-turn conversations in applications or services that require persistent context.
Description
- Purpose: Provide a structured environment for handling multiple user-bot interactions within one session.
- Key Features:
- Maintains an ordered conversation history as a list of
TextNode
s.
- Tracks and enforces a maximum number of user+assistant reply pairs.
- Allows attachments (images/files) to be provided in prompts.
- Integrates seamlessly with a
Bot
(and thus with a Model
) to generate responses.
- Metadata: Stores creation date, title, and calculates token-based costs from the conversation history.
Attributes
-
chat_time_format: str
The string format used for timestamps (by default "%d/%m/%Y %H:%M:%S"
).
-
chat_replies_limit: int
The maximum number of user+assistant reply pairs allowed in the session. If set to None
, there is no limit.
-
chat_history: list[TextNode]
A list of TextNode
instances, each representing a message (user or assistant) in the conversation.
-
chat_user: str
The identifier for the user participating in the chat (e.g., "User"
, "Alice"
, etc.).
-
chat_creation_date: str
A timestamp indicating when the chat session was created, using chat_time_format
.
-
chat_replies: int
The current number of user+assistant message pairs in the conversation (computed from chat_history
).
-
chat_cost: int
The sum of all tokens used across the conversation. Calculated by summing the tokens
attribute of each TextNode
.
-
chat_title: str
A descriptive title for the chat session (e.g., "New Chat"
, "Customer Support Session"
, etc.).
Methods
Constructor
__init__(*args, title="New Chat", replies_limit=10, user="User", cw_bot=None, **kwargs)
Creates a new chat session with an optional title, reply limit, user identifier, and existing Bot
. If no Bot
is provided, the constructor creates one internally (inheriting from Bot
).
-
Parameters:
*args
: Positional arguments passed to the underlying Bot
or Model
constructor, if cw_bot
is not provided.
title (str)
: The chat title ("New Chat"
by default).
replies_limit (int | None)
: The maximum number of user+assistant pairs. None
means no limit (defaults to 10
).
user (str)
: Name or identifier of the user ("User"
by default).
cw_bot (Bot | None)
: An existing Bot
. If None
, a new one is created.
**kwargs
: Additional keyword arguments passed to the Bot
or Model
constructor if cw_bot
is not provided.
-
Raises:
TypeError
: If cw_bot
is provided but is not a Bot
instance.
__str__() -> str
Returns a concise string representation, including the chat title, reply limit, current reply count, and creation date.
__repr__() -> str
Returns a detailed string for debugging or logging, including attributes such as the reply limit, user, title, and the associated Bot
.
__lt__(other: Chat) -> bool
Enables chronological sorting by comparing chat_creation_date
between two Chat
instances.
__eq__(other: Chat) -> bool
Checks if two Chat
objects have the same essential attributes: reply limit, user, title, creation date, number of replies, history, and the same underlying Bot
.
Properties
-
chat_Bot (getter)
Retrieves the underlying Bot
instance. Useful if you need direct access to its rules or the Model
it uses.
-
chat_time_format (getter/setter)
- Getter: Returns the time format string for timestamps.
- Setter: Validates a new time format by attempting to format the current time. Raises a
ValueError
if invalid.
-
chat_replies_limit (getter/setter)
- Getter: Returns the maximum allowed replies (
float('inf')
if no limit).
- Setter: Accepts an integer or
None
(treated as infinite). Raises a TypeError
if the value cannot be converted to int
.
-
chat_history (getter/setter)
- Getter: Returns a list of the conversation’s
TextNode
messages.
- Setter: Accepts a list of
TextNode
s or a list of dictionaries convertible to TextNode
. Raises a TypeError
for invalid data.
-
chat_user (getter/setter)
- Getter: Returns the user’s name or identifier.
- Setter: Assigns a new user identifier as a
str
.
-
chat_creation_date (getter/setter)
- Getter: Returns the creation date as a
str
formatted according to chat_time_format
.
- Setter: Validates that the given string matches the current time format; raises
ValueError
if incorrect.
-
chat_replies (getter)
Calculates the number of user+assistant pairs from the chat_history
. Each pair corresponds to two messages (one user and one assistant).
-
chat_cost (getter)
Sums the tokens
attribute of each TextNode
in chat_history
to provide a cumulative token usage.
-
chat_title (getter/setter)
- Getter: Retrieves the session’s title.
- Setter: Assigns a new session title as a
str
.
set_all(return_self: bool = True, **kwargs: Any) -> Chat | None
Dynamically updates multiple attributes on the Chat
(and on its underlying Bot
or Model
) using keyword arguments.
- Usage: Each key should refer to the private attribute path (e.g.,
"_Chat__user"
, "_Bot__rules"
, "_Model__api_key"
).
- Returns: The
Chat
instance itself if return_self
is True
, otherwise None
.
Generating Responses
Use get_response
to interact with the chat:
get_response(prompt: str, user: str | None = None, image_path: str | None = None, file_path: str | None = None) -> str
Generates an assistant’s response to the given prompt
, optionally attaching an image or file. The new user and assistant messages are appended to chat_history
as TextNode
objects.
-
Parameters:
prompt (str)
: The user’s text input.
user (str | None)
: Custom user identifier for this prompt. Defaults to chat_user
if None
.
image_path (str | None)
: Path to an image file to include in the request.
file_path (str | None)
: Path to a file to upload for reference.
-
Returns:
- A
str
containing the assistant’s response content.
Example Usage
from chatweaver import Chat, Bot, TextNode
my_bot = Bot(api_key="sk-1234567890abcdef1234...", rules="You are a friendly assistant.")
my_chat = Chat(
cw_bot=my_bot,
title="My AI Discussion",
replies_limit=5,
user="Alice"
)
assistant_reply = my_chat.get_response("Hello, can you tell me a joke?")
print("Assistant says:", assistant_reply)
for node in my_chat.chat_history:
print(node.role, node.content, sep=": ")
print("Total token usage:", my_chat.chat_cost)
my_chat.chat_title = "Jokes and More"
my_chat.set_all(
_Chat__replies_limit=None,
_Bot__rules="Stay professional in your answers."
)
print("Updated chat limit:", my_chat.chat_replies_limit)
print("Updated bot rules:", my_chat.chat_Bot.bot_rules)
In this example, you create a Chat
using a specific Bot
, ask for a response, and then manipulate or inspect the Chat
’s properties. The session automatically tracks message history (via TextNode
objects), token usage, and enforces reply limits (if specified).
CWArchive
The CWArchive class provides a convenient way to store and manage Chat
, Bot
, or Model
objects in a single file. It supports both synchronous and asynchronous loading, as well as basic add, remove, and retrieval operations. This makes it suitable for saving and restoring chatbot sessions, bots, or model configurations in a controlled, persistent manner.
Description
- Purpose: Encapsulate file-based storage of
Chat
, Bot
, and Model
objects with minimal setup.
- Asynchronous Support: Can operate in asynchronous mode, splitting the data into chunks and loading them in parallel.
- Archive Operations: Offers methods to add or remove objects (by ID or directly), retrieve the entire archive, and save changes back to disk.
- Thread-Safety Caveat: Running operations asynchronously in certain environments can lead to race conditions. If this happens, the delay or asynchronous behavior can be tweaked to mitigate the issue.
- Context Manager: The
__enter__
and __exit__
methods handle setup and teardown logic, ensuring that save() is automatically called on exit.
Attributes
-
archive_path: str
The filesystem path where the archive is stored.
-
archive_data: dict
A dictionary of stored objects, keyed by integer IDs.
-
archive_id: int
The next available integer ID that can be used when adding a new item.
-
archive_delay: float
The delay (in seconds) between asynchronous load operations. Adjust this if you experience race conditions in multi-threaded contexts.
-
archive_asynchronous: bool
Controls whether the archive is loaded/saved asynchronously. If set to False
, data loading is done sequentially.
Methods
Below is an overview of the most important methods in CWArchive.
Constructor
__init__(path: str, asynchronous: bool = True, delay: float = 0.07)
Initializes a new archive with the specified file path, async flag, and an optional delay for asynchronous loads.
__str__() -> str
Returns a user-friendly string with the path of the archive, e.g., "<Archive | path=/path/to/archive>"
.
__repr__() -> str
Returns a formal string representation of the archive for debugging purposes, e.g., "Archive(path='...')"
.
__len__() -> int
Returns the count of items (key-value pairs) currently in the archive.
__add__(other: Chat | Bot | Model)
Enables using the +
operator to add a Chat
, Bot
, or Model
object to the archive.
my_archive = CWArchive("chats.archive")
my_archive + my_chat
__sub__(other: int | Chat | Bot | Model)
Enables using the -
operator to remove an element from the archive by its integer ID or by an instance of Chat
, Bot
, or Model
.
my_archive - 0
my_archive - my_chat
__enter__(self, *args, **kwargs) -> CWArchive
Returns the CWArchive
instance upon entering a with block, allowing you to perform archive operations within that scope.
with CWArchive(path="my_archive.txt") as archive:
...
__exit__(self, *args, **kwargs) -> None
Called automatically upon exiting the with block. By default, it invokes save(), ensuring that any modifications made to the archive during the block are persisted to disk.
with CWArchive(path="my_archive.txt") as archive:
Core Archive Actions
-
define(path: str, delay: float) -> None
Updates the archive file path and the delay for asynchronous operations.
-
get_id_from_element(element: Chat | Bot | Model) -> list[int]
Returns a list of all IDs in the archive that correspond to the provided object. Useful if the same object was stored multiple times.
-
is_valid_id(identifier: int) -> bool
Checks whether the given ID exists in the archive.
-
add(element: Chat | Bot | Model) -> None
Inserts an element (if it is an instance of Chat
, Bot
, or Model
) into the archive under the next available integer ID.
-
remove(element: list | set | tuple | int | Chat | Bot | Model, remove_type: str | None = "all") -> None
Removes one or multiple elements from the archive.
element
: Can be a single ID, a single object, or an iterable of IDs/objects.
remove_type
: Accepts "all"
, "first"
, or "last"
to handle cases where the same object appears more than once.
-
save() -> None
Serializes and writes the archive data to disk in sorted order (by the object’s representation).
my_archive.save()
-
retrieve() -> dict[int, Chat | Bot | Model]
Reads the archive file from disk and converts each item’s string representation into its corresponding object. Supports both synchronous and asynchronous loading.
- Asynchronous: Splits the data into chunks and loads them concurrently, separated by
archive_delay
seconds.
- Synchronous: If
archive_asynchronous
is False
, loads data one item at a time.
Usage Example
from chatweaver import CWArchive, Chat
archive = CWArchive(path="my_archive.txt")
my_chat = Chat(api_key="sk-1234567890abcdef...", title="Session One")
archive.add(my_chat)
archive.save()
loaded_data = archive.archive_data
for item_id, item_obj in loaded_data.items():
print(item_id, item_obj)
archive.remove(0)
archive.save()
archive.archive_asynchronous = False
In this example, we initialize a CWArchive
, add a Chat
to it, and then save and retrieve data. If concurrency errors appear due to asynchronous operations, you can either increase archive_delay
or set archive_asynchronous
to False
to force sequential processing.
Using the Context Manager
from chatweaver import CWArchive, Bot
my_bot = Bot(api_key="sk-1234567890abcdef...", rules="Be helpful!", name="HelperBot")
with CWArchive(path="my_archive.txt") as archive:
archive.add(my_bot)
When using CWArchive as a context manager:
__enter__
is called at the start of the with block, returning the instance.
__exit__
is automatically called at the end, ensuring save() is executed.