
Security News
CVE Volume Surges Past 48,000 in 2025 as WordPress Plugin Ecosystem Drives Growth
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.
chatweaver
Advanced tools
A Python library that simplifies building AI-driven chatbots with OpenAI, offering conversation management, file attachments, and persistent storage in one cohesive package.
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.
Chat sessions or multiple objects with CWArchive.ModelA wrapper for OpenAI-based models, ensuring valid API keys and model names.
Initialization:
from chatweaver import Model
# Provide an API key and optionally specify the model name
model = Model(api_key="sk-1234567890abcdef1234...", model="gpt-4o")
Key Capabilities:
BotExtends Model to create a customizable AI assistant with definable rules and a unique name.
Initialization:
from chatweaver import Bot
bot = Bot(
api_key="sk-1234567890abcdef1234...",
rules="You are a helpful assistant that responds concisely.",
name="ConciseBot"
)
Key Capabilities:
response(...) method to generate replies to user prompts, including support for file attachments.TextNodeA lightweight, immutable container for individual messages in a conversation.
Example:
from chatweaver import TextNode
# Represents a user's message
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:
ChatBuilds 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
# Pass an existing Bot or let Chat create its own under the hood
chat_session = Chat(
api_key="sk-1234567890abcdef1234...",
title="Support Session",
replies_limit=5,
user="Customer"
)
Key Capabilities:
chat_history.chat_replies_limit).get_response(...) to seamlessly generate and store new user and assistant messages:
user_input = "Hi! I need help with my order."
assistant_reply = chat_session.get_response(prompt=user_input)
print("Assistant:", assistant_reply)
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.CWArchiveHandles 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:
Usage:
# Suppose we have a Chat object we want to store
chat_session = Chat(api_key="sk-1234567890abcdef1234...", title="My Chat Session")
# Add the chat to the archive
archive.add(chat_session)
# Save changes to disk
archive.save()
# Retrieve all objects (may load asynchronously in parallel)
data = archive.archive_data
for item_id, chat_obj in data.items():
print(item_id, chat_obj)
# Remove an item (by ID or by reference)
archive.remove(0) # remove the object with ID = 0
# or
archive.remove(chat_session)
Below is a high-level example illustrating how you might use ChatWeaver end-to-end:
from chatweaver import Model, Bot, Chat, CWArchive
# 1. Create a Model
model = Model(api_key="sk-1234567890abcdef1234", model="gpt-4o")
# 2. Create a Bot with custom rules, tied to the Model
rules = "You are a friendly and knowledgeable assistant."
bot = Bot(
cw_model=model,
rules=rules,
name="HelpfulBot"
)
# 3. Start a Chat session using the Bot
chat = Chat(
cw_bot=bot,
title="Demo Session",
replies_limit=3, # limit to 3 user+assistant replies
user="Tester"
)
# 4. Interact with the Chat
response1 = chat.get_response("Hello, how are you?")
print("Bot:", response1)
response2 = chat.get_response("Can you tell me a joke?")
print("Bot:", response2)
# Check Chat details
print("Current replies used:", chat.chat_replies)
print("Total tokens so far:", chat.chat_cost)
# 5. Save the Chat to an Archive
archive = CWArchive(path="demo_archive.txt", asynchronous=True, delay=0.07)
archive.add(chat)
archive.save()
# 6. Retrieve data from the archive and display
loaded_data = archive.archive_data
for item_id, obj in loaded_data.items():
print(f"Item ID {item_id} -> {obj}")
In this script:
Model.Bot. We optionally limit how many user+assistant pairs are allowed.Chat to an archive file and later re-load it to continue the session.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.
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.
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.
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.
api_key (str): Must begin with "sk-" and be at least 20 characters.model (str): The model name to use, defaulting to "gpt-4o".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.
from chatweaver import Model
# Initialize the Model with a valid API key and optionally specify the model name
model_instance = Model(api_key="sk-1234567890abcdef1234", model="gpt-4o")
# Check basic info
print(str(model_instance))
# Example Output: <Model | model=gpt-4o, api_key=sk-1234567...4564>
# Update the model name (must be in the allowed set of models)
model_instance.model_model = "gpt-4o"
# Update the API key (re-initializes the OpenAI client if validation succeeds)
model_instance.model_api_key = "sk-0987654321abcdef5678"
# Retrieve the OpenAI client to make requests manually (if needed)
client = model_instance.model_client
# Compare with another Model instance
another_model = Model(api_key="sk-0987654321abcdef5678", model="gpt-4o")
print(model_instance == another_model) # True, if both model name and API key match
Use the Model class to simplify how you manage different OpenAI models and keys across your chatbot applications.
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.
Model.Model instance, you can pass it in; otherwise, the Bot will initialize its own Model by inheriting from Model.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.
Below is a summary of the key methods in the Bot class.
__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__() -> strReturns a friendly string with the bot’s name, for quick identification.
__repr__() -> strReturns a more detailed string representation, useful for debugging (includes name, rules, and the underlying Model).
__eq__(other: Bot) -> boolCompares 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)None, defaults to a predefined rule set.bot_name (getter/setter)bot_time_format (getter/setter)ValueError if the format is invalid.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.
Parameters:
prompt (str): The user’s query or statement.user (str): A label for the user in conversation (default is "User").history (list | None): Optional prior messages to maintain context.image_path (str | None): Local path to an image to be included.file_path (str | None): Local path to any file to upload and reference.Returns (dict[str, Any]):
"content": The text generated by the model."prompt_tokens", "completion_tokens", "total_tokens": Token usage statistics."start_date": Timestamp at which the request started."final_date": Timestamp at which the request finished."delta_time": Elapsed time for the model to produce the response.from chatweaver import Bot, Model
# 1. Using an existing 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"
)
# 2. Or let Bot create a new Model internally
another_bot = Bot(
api_key="sk-0987654321abcdef5678...", # Passed to Model's constructor
rules="You are a formal assistant that provides concise answers.",
name="ConciseBot"
)
# 3. Generating a response to a user prompt
response_data = bot.response(
prompt="What is the capital of France?",
user="Alice",
image_path=None,
file_path=None
)
# Check response
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.
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.
TextNode is defined with @dataclass(frozen=True), meaning its fields cannot be modified after creation.TextNode encapsulates one message. In a broader chatbot flow, multiple TextNode instances together form the conversation history.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.
__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).
Below is a simple demonstration of how you might create a TextNode and then inspect its attributes.
from chatweaver import TextNode
# Create a TextNode representing a user's message
user_message = TextNode(
role="user",
content="What's the weather like today?",
owner="Alice",
tokens=8,
date="2025-01-01 09:15:00"
)
# Print the node in a JSON-like string representation
print(str(user_message))
# Output:
# {
# 'role': 'user',
# 'content': "What's the weather like today?",
# 'owner': 'Alice',
# 'tokens': 8,
# 'date': '2025-01-01 09:15:00'
# }
# Convert the TextNode to a dictionary for further processing
node_dict = dict(user_message)
print(node_dict["content"]) # "What's the weather like today?"
Use TextNode objects to store and pass around conversation messages reliably, ensuring their metadata remains intact across different parts of your chatbot or application.
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.
TextNodes.Bot (and thus with a Model) to generate responses.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.).
__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__() -> strReturns a concise string representation, including the chat title, reply limit, current reply count, and creation date.
__repr__() -> strReturns a detailed string for debugging or logging, including attributes such as the reply limit, user, title, and the associated Bot.
__lt__(other: Chat) -> boolEnables chronological sorting by comparing chat_creation_date between two Chat instances.
__eq__(other: Chat) -> boolChecks if two Chat objects have the same essential attributes: reply limit, user, title, creation date, number of replies, history, and the same underlying Bot.
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)
ValueError if invalid.chat_replies_limit (getter/setter)
float('inf') if no limit).None (treated as infinite). Raises a TypeError if the value cannot be converted to int.chat_history (getter/setter)
TextNode messages.TextNodes or a list of dictionaries convertible to TextNode. Raises a TypeError for invalid data.chat_user (getter/setter)
str.chat_creation_date (getter/setter)
str formatted according to chat_time_format.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)
str.set_all(return_self: bool = True, **kwargs: Any) -> Chat | NoneDynamically updates multiple attributes on the Chat (and on its underlying Bot or Model) using keyword arguments.
"_Chat__user", "_Bot__rules", "_Model__api_key").Chat instance itself if return_self is True, otherwise None.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:
str containing the assistant’s response content.from chatweaver import Chat, Bot, TextNode
# 1. Creating a Chat with a custom Bot
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"
)
# 2. Getting a response
assistant_reply = my_chat.get_response("Hello, can you tell me a joke?")
print("Assistant says:", assistant_reply)
# 3. Accessing chat history
for node in my_chat.chat_history:
print(node.role, node.content, sep=": ")
# 4. Viewing total token usage
print("Total token usage:", my_chat.chat_cost)
# 5. Changing chat title
my_chat.chat_title = "Jokes and More"
# 6. Setting multiple attributes at once
my_chat.set_all(
_Chat__replies_limit=None, # No longer limit the replies
_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).
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.
Chat, Bot, and Model objects with minimal setup.__enter__ and __exit__ methods handle setup and teardown logic, ensuring that save() is automatically called on exit.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.
Below is an overview of the most important methods in CWArchive.
__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.
Parameters:
path (str): The path to the archive file on disk.asynchronous (bool): Determines if loading is handled asynchronously (True by default).delay (float): Time in seconds to wait between asynchronous load operations. Defaults to 0.07.Raises:
TypeError: If asynchronous is not a boolean.Potential Race Condition:
If the asynchronous loading processes attempt to modify shared data at the same time, errors can occur. Consider increasing delay or setting archive_asynchronous to False.
__str__() -> strReturns a user-friendly string with the path of the archive, e.g., "<Archive | path=/path/to/archive>".
__repr__() -> strReturns a formal string representation of the archive for debugging purposes, e.g., "Archive(path='...')".
__len__() -> intReturns 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 # Adds 'my_chat' under the next available ID
__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 # Removes the item with ID 0
my_archive - my_chat # Removes the specific Chat instance
__enter__(self, *args, **kwargs) -> CWArchiveReturns 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) -> NoneCalled 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:
# All changes, like adding or removing objects,
# will be saved when exiting this block.
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() # Commits changes to file
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.
archive_delay seconds.archive_asynchronous is False, loads data one item at a time.from chatweaver import CWArchive, Chat
# 1. Create an archive (asynchronous by default) with a specific file path
archive = CWArchive(path="my_archive.txt")
# 2. Add a new Chat object to the archive
my_chat = Chat(api_key="sk-1234567890abcdef...", title="Session One")
archive.add(my_chat)
# 3. Save the archive to disk
archive.save()
# 4. Retrieve data from the archive (asynchronously, in chunks by default)
loaded_data = archive.archive_data # Remember, it's better to use the archive_data attribute instead of retrieve()
for item_id, item_obj in loaded_data.items():
print(item_id, item_obj)
# 5. Remove the chat by its ID (e.g., 0)
archive.remove(0)
archive.save() # Reflect changes on disk
# 6. Disable asynchronous loading if you run into concurrency issues
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.
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)
# No need to manually call archive.save()
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.FAQs
A Python library that simplifies building AI-driven chatbots with OpenAI, offering conversation management, file attachments, and persistent storage in one cohesive package.
We found that chatweaver demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.

Security News
Socket CEO Feross Aboukhadijeh joins Insecure Agents to discuss CVE remediation and why supply chain attacks require a different security approach.

Security News
Tailwind Labs laid off 75% of its engineering team after revenue dropped 80%, as LLMs redirect traffic away from documentation where developers discover paid products.