A Python library that simplifies building AI-driven chatbots with OpenAI, offering conversation management, file attachments, and persistent storage in one cohesive package.
Project description
chatweaver
A Python library that simplifies building AI-driven chatbots with OpenAI, offering conversation management, file attachments, and persistent storage in one cohesive package.
Overview
What it is
chatweaver is a Python library for building chatbots on top of OpenAI, with:
- A
Modelwrapper for OpenAI client access and API key validation - A
Botabstraction for creating chat completions (including optional images and file attachments) - A
Chatsession that manages conversation history and reply limits - A
Schemahelper to request structured JSON outputs - An
Archiveformat (.cwarchive) for saving/loadingModel,Bot,Chat, andTextNodeobjects
Why it exists / main value
It provides a cohesive set of building blocks to:
- Manage conversations with history
- Attach images and upload files (local paths) to OpenAI
- Persist and restore bots/chats/models (with secrets excluded by default)
Key features
-
Conversation management via
Chatwith message history (TextNode) and reply limits -
OpenAI model wrapper with lazy API key validation (
Model) -
Support for attachments:
- Images from local paths (embedded as base64 data URLs) or from URLs
- Files from local paths (uploaded) or file IDs
-
Structured outputs via
Schema(response_formatJSON schema) -
Persistence to a binary
.cwarchivefile viaArchive(atomic save + integrity checks) -
Freeze/thaw snapshots for
Model,Bot,Chat,Schema, andTextNode
Typical use cases
- Build an OpenAI-powered chatbot with a managed conversation history
- Add image inputs (URL or local path) to a prompt
- Upload local files and attach them to the request
- Enforce a rolling chat history using a reply limit
- Save and restore chat sessions/bots/models without storing API keys by default
Installation
Requirements
-
Python >= 3.10
-
OS Independent (per project classifiers)
-
Dependency:
openai==2.3.0
Install via pip
pip install chatweaver
Quickstart
Minimal working example
The code below demonstrates creating a chat session and generating a response.
from chatweaver import Model, Bot, Chat
# Provide your API key to enable remote services
model = Model(api_key="TODO: set your OpenAI API key", model="gpt-4o")
bot = Bot(model=model, name="AI Bot")
chat = Chat(bot=bot, title="New Chat", user="User", replies_limit=10)
reply = chat.response("Hello! What can you do?")
print(reply)
Usage examples
1) Use Chat with rolling history (reply limit)
Chat stores history as TextNode pairs (user + assistant). When the reply limit is reached, the oldest pair is dropped.
from chatweaver import Model, Bot, Chat
model = Model(api_key="TODO: set your OpenAI API key")
bot = Bot(model=model, name="AI Bot")
chat = Chat(bot=bot, replies_limit=2)
print(chat.response("First message"))
print(chat.response("Second message"))
print(chat.response("Third message (oldest pair will be dropped)"))
print("Replies:", chat.replies)
print("Token cost (sum of stored nodes):", chat.cost)
2) Request structured output with Schema
Schema.resolve() produces an OpenAI-compatible response_format with json_schema and strict=True.
from chatweaver import Model, Bot, Schema
schema = Schema(
name="AnswerSchema",
properties={
"answer": {"type": "string"},
"confidence": {"type": "number"},
},
)
model = Model(api_key="TODO: set your OpenAI API key")
bot = Bot(model=model, schema=schema)
result = bot.completion("Explain what JSON Schema is in one sentence.")
print(result.content)
3) Attach images (URL or local path)
Images can be:
- URLs (must be valid URL)
- Local paths (converted to
data:image/png;base64,...)
from chatweaver import Model, Bot
model = Model(api_key="TODO: set your OpenAI API key")
bot = Bot(model=model)
# URL-based image
result = bot.completion(
prompt="Describe what you see in the image.",
img_data="https://example.com/some-image.png",
)
print(result.content)
# Local image path (embedded as base64 data URL)
result = bot.completion(
prompt="Describe what you see in the image.",
img_data="TODO: path/to/local-image.png",
)
print(result.content)
4) Attach files (local path upload or file_id)
Files can be:
- A file ID (already uploaded)
- A local path (uploaded via the OpenAI Files API with purpose="user_data")
- URLs are not supported for files (will raise
ValueError)
from chatweaver import Model, Bot
model = Model(api_key="TODO: set your OpenAI API key")
bot = Bot(model=model)
# Attach a file_id (already uploaded elsewhere)
result = bot.completion(
prompt="Summarize the attached document.",
file_data="TODO: existing-file-id",
)
print(result.content)
# Attach a local file path (will be uploaded)
result = bot.completion(
prompt="Summarize the attached document.",
file_data="TODO: path/to/document.pdf",
)
print(result.content)
API Reference
Package exports
The top-level package exports:
SchemaTextNodeModelBotChatArchiveChatWeaverModelNames,ChatWeaverSystemRules,Formatting,Language
from chatweaver import (
Schema,
TextNode,
Model,
Bot,
Chat,
Archive,
ChatWeaverModelNames,
ChatWeaverSystemRules,
Formatting,
Language,
)
Model
Wrapper around OpenAI client access with lazy validation.
class Model:
def __init__(self, api_key: Optional[str] = None, model: str = ChatWeaverModelNames.gpt_4o, **kwargs) -> None: ...
def freeze(self, include_secrets: bool = False) -> dict[str, Any]: ...
@classmethod
def thaw(cls, snapshot: dict[str, Any], api_key: Optional[str] = None) -> "Model": ...
def api_key_hint(self) -> str: ...
def validate_api_key(self) -> bool: ...
@property
def client(self) -> openai.OpenAI: ...
def can_use_remote_services(self) -> bool: ...
Notes
api_keyis not validated on set. It is validated whenclientis accessed (lazy validation).- If the API key format is invalid (does not start with
"sk-"or is too short),key_statusbecomesINVALIDandlast_auth_erroris set to"Invalid API key format.". - Accessing
clientwhen the key cannot be validated raisesRuntimeErrorwith the reason.
Bot
Conversational bot that uses a Model instance.
class Bot:
def __init__(
self,
model: Optional[Model] = None,
rules: Optional[str] = ChatWeaverSystemRules.default(),
name: str = "AI Bot",
schema: Optional[Schema] = None,
time_format: str = "%d/%m/%Y %H:%M:%S",
**kwargs,
) -> None: ...
def freeze(self, include_secrets: bool = False) -> dict[str, Any]: ...
@classmethod
def thaw(
cls,
snapshot: dict[str, Any],
model: Optional[Model] = None,
api_key: Optional[str] = None,
) -> "Bot": ...
def completion(
self,
prompt: str,
user: str = "User",
history: list | None = None,
img_data: str | pathlib.Path | list[str | pathlib.Path] | None = None,
file_data: str | pathlib.Path | FileObject | list[str | pathlib.Path | FileObject] | None = None,
response_schema: Schema | None = None,
) -> "BotCompletionResult": ...
Parameters
-
prompt: The user prompt string. -
user: Name of the user (included in the system prompt). -
history: Optional message list prepended to the constructed system/user messages. InChat, this is built fromTextNodeentries viadict(node). -
img_data:str/pathlib.Path: a single image URL or local pathlist[str|pathlib.Path]: multiple image URLs/paths- Local paths are base64-encoded into a
data:image/png;base64,...URL.
-
file_data:str/pathlib.Path: a local path (uploaded) or a file ID stringFileObject: OpenAI file object (treated as a file ID)list[...]: multiple local paths / file IDs /FileObjects- URLs are not supported for files.
-
response_schema: ASchemato use for this request. If omitted,Bot.schemais used.
Returns
-
BotCompletionResultcontaining:content(assistant message content or refusal)- token usage (
prompt_tokens,completion_tokens,total_tokens) - timestamps (
start_date,final_date) and timing (delta_time) - input/output metadata (
MetadataContainer)
Raised exceptions (selected)
TypeErrorifimg_data,file_data, orresponse_schematypes are invalidValueErrorif an image path/URL is invalid, or iffile_datais a URL (unsupported)RuntimeErrorfromModel.clientif remote services are not available due to missing/invalid key- Other exceptions may bubble up from the OpenAI client
Chat
Chat session that keeps history and uses a Bot.
class Chat:
def __init__(
self,
bot: Optional[Bot] = None,
title: str = "New Chat",
replies_limit: int | None = 10,
user: str = "User",
time_format: str = "%d/%m/%Y %H:%M:%S",
creation_date: Optional[str] = None,
history: Optional[list[TextNode] | list[dict[str, Any]]] = None,
**kwargs,
) -> None: ...
def freeze(self, include_secrets: bool = False) -> dict[str, Any]: ...
@classmethod
def thaw(
cls,
snapshot: dict[str, Any],
bot: Optional[Bot] = None,
api_key: Optional[str] = None,
) -> "Chat": ...
def response(
self,
prompt: str,
user: Optional[str] = None,
image_path: Optional[str] = None,
file_path: Optional[str] = None,
) -> str: ...
@property
def replies(self) -> int: ...
@property
def cost(self) -> int: ...
Notes
-
replies_limit=Nonemeans no limit (internallyfloat("inf")). -
historyaccepts:list[TextNode]list[dict](either frozen snapshots or plain dict payloads)
-
response()appends a userTextNodeand an assistantTextNodeto history (dropping oldest pairs when at limit).
Archive
Persistence for Chat, Bot, Model, and TextNode to a binary .cwarchive file.
class Archive:
def __init__(self, path: str, api_key: str | None = None, asynchronous: bool = True, delay: float = 0.07) -> None: ...
@property
def data(self) -> dict[int, Chat | Bot | Model]: ...
def add(self, element: Chat | Bot | Model) -> None: ...
def remove(self, element: list | tuple | int | Chat | Bot | Model, remove_type: str = "all") -> None: ...
def save(self, path: str | None = None, include_secrets: bool = False) -> None: ...
def retrieve(
self,
path: str | None = None,
api_key: str | None = None,
api_key_provider: Optional[Callable[[int, int, dict[str, Any]], Optional[str]]] = None,
) -> dict[int, Any]: ...
Notes
Archive.pathensures parent directories exist and creates an empty file if missing.save(include_secrets=False)writes snapshots without API keys by default.retrieve(..., api_key=..., api_key_provider=...)can inject API keys during restoration (keys are not stored unless saved withinclude_secrets=True).- When
asynchronous=True, object reconstruction usesasyncio+asyncio.to_thread.
Schema
A JSON schema container for structured model outputs.
@dataclass(frozen=True)
class Schema:
name: str
properties: dict[str, Any]
@property
def required(self) -> list[str]: ...
def resolve(self) -> dict: ...
def freeze(self) -> dict[str, Any]: ...
@classmethod
def thaw(cls, snapshot: dict[str, Any]) -> "Schema": ...
TextNode
Immutable message node with metadata.
@dataclass(frozen=True)
class TextNode:
role: str
content: str
owner: str
tokens: int
date: str
image_data: list[Any]
file_data: list[Any]
def freeze(self) -> dict[str, Any]: ...
@classmethod
def thaw(cls, snapshot: dict[str, Any]) -> "TextNode": ...
Configuration
Models
ChatWeaverModelNames provides predefined model name strings and allows controlled extension:
-
Built-in fields include:
"gpt-4","gpt-4o","gpt-4-turbo","o1","o1-mini","o3"
from chatweaver import ChatWeaverModelNames
print(ChatWeaverModelNames.gpt_4o)
print(ChatWeaverModelNames.get_all_models())
ChatWeaverModelNames.add("claude-3-opus") # adds a new model name (if valid)
ChatWeaverModelNames.delete("claude-3-opus") # deletes it (if present)
System rules, formatting, language
ChatWeaverSystemRules returns predefined instruction strings for system prompts, optionally augmented with:
Formattingenum presets (e.g.,Formatting.plain_text,Formatting.markdown, etc.)Languageenum presets (e.g.,Language.conversation_mirroring,Language.EN, etc.)
from chatweaver import ChatWeaverSystemRules, Formatting, Language
rules = ChatWeaverSystemRules.default(formatting=Formatting.markdown, language=Language.EN)
Troubleshooting / FAQ
RuntimeError: Client not available: key_status=<MISSING>...
- Cause:
Model.api_keyis not set, and the code tries to accessModel.client(e.g., duringBot.completion()or file uploads). - Fix: Provide a valid API key when constructing
Model, or setmodel.api_key = "..."before calling remote operations.
RuntimeError: Client not available: key_status=<INVALID>... Invalid API key format.
- Cause: API key does not start with
"sk-"or is too short. - Fix: Set a properly formatted key (must start with
"sk-"and be length >= 20, per the library’s format check).
ValueError: <Invalid file source (URL not supported): ...>
- Cause:
file_datawas passed as a URL string. - Fix: Use a local file path (it will be uploaded) or pass an existing file ID.
ValueError: <Invalid image path or url: ...>
- Cause:
img_datais neither a valid URL nor an existing local path. - Fix: Provide a valid URL (
scheme+netloc) or a local file path that exists.
TypeError: <img_data must be a string, pathlib.Path, or list of strings/paths>
- Cause:
img_datais a wrong type (e.g., dict or bytes). - Fix: Pass
str,pathlib.Path, a list of those, orNone.
TypeError: <file_data must be a string, pathlib.Path, FileObject, or list of them>
- Cause:
file_datais a wrong type. - Fix: Pass a local path (
str/pathlib.Path), an OpenAIFileObject, a file ID string, a list of those, orNone.
ValueError: <Invalid 'time_format': expected a valid strftime format>
- Cause:
Bot.time_formatorChat.time_formatwas set to an invalidstrftimeformat string. - Fix: Use a valid
time.strftimeformat, such as"%d/%m/%Y %H:%M:%S".
ValueError: <Invalid 'creation_date': expected format '%d/%m/%Y %H:%M:%S'>
- Cause:
Chat.creation_datedoes not match the configuredChat.time_format. - Fix: Ensure
creation_datematchestime_format, or omit it to auto-generate.
- Archive load error:
<Invalid cwarchive: bad magic>or<Unsupported cwarchive version: ...>
- Cause: The archive file is not a valid
.cwarchivefile or uses an unsupported version. - Fix: Ensure you saved the archive using
Archive.save()from a compatible version ofchatweaver.
- Archive load error:
<Corrupted payload (id=...): checksum mismatch>
- Cause: Archive file corruption or incomplete writes.
- Fix: Restore from a backup.
Archive.save()writes atomically using a temporary file andos.replace(), so corruption may indicate external modification or storage issues.
License
MIT
Credits / Authors / Acknowledgements
Cecchelani Diego
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file chatweaver-1.2.0.tar.gz.
File metadata
- Download URL: chatweaver-1.2.0.tar.gz
- Upload date:
- Size: 37.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e7167807ce37807b09cf61ec8e3b7192c9d2dac013dfc67f654eacb51bb6d1f
|
|
| MD5 |
7c72118b68c89ce108550a33c13854bc
|
|
| BLAKE2b-256 |
9d48b5c678767b02b5f7a09a930d9e5aa377840df52de02974469925f3881f21
|
File details
Details for the file chatweaver-1.2.0-py3-none-any.whl.
File metadata
- Download URL: chatweaver-1.2.0-py3-none-any.whl
- Upload date:
- Size: 36.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5ab6bf2fe3a217ea7e7241b4d007da1bbf8b22e13058226dc8807f3110bfed4
|
|
| MD5 |
33478bcbc1ad1f5bc19f5430283f41bc
|
|
| BLAKE2b-256 |
3b1958fad9c22d7ecf2acdc5587c0ca6f272d133b69deba507f5b5d9daeecb64
|