I built an open-source Python Mini SDK for Google Gemini from scratch 🧛
I'm a computer engineering student from Turkey, and over the past 5 days
I built Dracula. It's an open-source Python Mini SDK for Google Gemini AI.
I started this project because I wanted to learn how real Python libraries
are built, published, and maintained. What started as a simple wrapper
quickly grew into a full Mini SDK with features I'm really proud of.
In this article, I'll share everything I learned along the way.
🚀 What is Dracula?
Dracula is a Python Mini SDK for Google Gemini that makes it easy to
add AI to any Python project. It has a clean API, full async support,
function calling, and much more.
Install it with:
pip install dracula-ai
Basic usage:
from dracula import Dracula
ai = Dracula(api_key="your-api-key")
response = ai.chat("Hello, who are you?")
print(response)
🛠️ The Coolest Feature: Function Calling
The most powerful feature of Dracula is the @tool decorator system.
You can give Gemini access to any Python function, and it will
automatically decide when and how to call it based on the user's message.
from dracula import Dracula, tool
@tool(description="Get the current weather for a city")
def get_weather(city: str) -> str:
# In real life this would call a weather API
return f"It's 25°C and sunny in {city}"
@tool(description="Search the web for information")
def search_web(query: str) -> str:
return f"Search results for: {query}"
ai = Dracula(
api_key="your-api-key",
tools=[get_weather, search_web]
)
# Gemini automatically calls get_weather("Istanbul")! 🤯
response = ai.chat("What's the weather in Istanbul?")
print(response)
# "The weather in Istanbul is currently 25°C and sunny!"
The @tool decorator automatically extracts the function's name,
parameters, and type hints and converts them into a schema that
Gemini can understand. No manual schema writing needed!
You can also handle tool calls manually if you want full control:
result = ai.chat("What's the weather in Ankara?", auto_call=False)
if result.requires_tool_call:
print(f"Tool: {result.tool_name}") # get_weather
print(f"Args: {result.tool_args}") # {"city": "Ankara"}
The Hardest Bug I Fixed — Thought Signatures
When I first implemented function calling, I kept getting this error:
400 INVALID_ARGUMENT: Function call is missing a thought_signature
After reading the Google documentation, I discovered that Gemini 3
models require thought signatures — encrypted tokens that preserve
the model's reasoning between tool calls. When I manually built the
conversation history, the thought signatures were lost.
The fix was to use Gemini's built-in persistent chat session which
handles thought signatures automatically:
# Wrong — loses thought signatures
response = self.client.models.generate_content(
model=self.model_name,
contents=history,
config=config
)
# Correct — handles thought signatures automatically
self._chat_session = self.client.chats.create(
model=self.model_name,
config=config
)
response = self._chat_session.send_message(message)
This was the most satisfying bug to fix in the entire project!
Async Support with AsyncDracula
For async applications like Discord bots and FastAPI, Dracula has
a separate AsyncDracula class with full async support:
import asyncio
from dracula import AsyncDracula, tool
@tool(description="Get the weather for a city")
async def get_weather(city: str) -> str:
return f"25°C and sunny in {city}"
async def main():
async with AsyncDracula(
api_key="your-api-key",
tools=[get_weather]
) as ai:
response = await ai.chat("What's the weather in Istanbul?")
print(response)
async for chunk in ai.stream("Tell me a story."):
print(chunk, end="", flush=True)
asyncio.run(main())
This design was inspired by httpx which has httpx.Client for sync
and httpx.AsyncClient for async — keeping the two completely separate
makes the API much cleaner.
Conversation Memory
Dracula automatically remembers the conversation history:
ai = Dracula(api_key="your-api-key")
ai.chat("My name is Ahmet.")
response = ai.chat("What is my name?")
print(response) # It remembers! ✅
# Save and load history
ai.save_history("conversation.json")
ai.load_history("conversation.json")
Professional Logging System
One of the things I'm most proud of is the logging system.
By default it's completely silent, but developers can turn it on:
# Simple logging
ai = Dracula(api_key="your-api-key", logging=True)
# With file rotation — creates new log file when size limit is reached
ai = Dracula(
api_key="your-api-key",
logging=True,
log_file="dracula.log",
log_max_bytes=1 * 1024 * 1024, # 1MB per file
log_backup_count=3 # keep 3 backups
)
I used Python's built-in RotatingFileHandler for this — when the
log file reaches the size limit, it automatically creates a new one
and keeps the specified number of backups.
Role Playing Mode
Dracula comes with 6 built-in personas:
ai.set_persona("pirate")
print(ai.chat("Hello, who are you?"))
# Arrr, I be a fearsome pirate! 🏴☠️
print(ai.list_personas())
# ['assistant', 'pirate', 'chef', 'shakespeare', 'scientist', 'comedian']
GeminiModel Enum
Instead of typing model names as strings which can cause typos,
Dracula provides a GeminiModel enum:
from dracula import Dracula, GeminiModel
ai = Dracula(api_key="your-api-key", model=GeminiModel.FLASH)
ai = Dracula(api_key="your-api-key", model=GeminiModel.PRO)
# Discover all available models in real time
print(ai.list_available_models())
CLI Tool
Dracula comes with a built-in CLI tool:
dracula chat "Hello!"
dracula chat "Tell me a joke" --persona comedian
dracula chat "Merhaba" --language Turkish --stream
dracula list-personas
dracula stats
71 Passing Tests
One of the most important things I learned was how to write proper
unit tests. Every feature has tests that check both the happy path
and error cases:
Final Results: 71 passed, 0 failed
Writing tests forced me to think about edge cases I never would
have considered otherwise. For example, I discovered that
validate_language wasn't being called in __init__ —
the tests caught it immediately!
What I Learned
Building Dracula taught me more than any tutorial ever could:
- How to structure and publish a real Python library to PyPI
- How
async/awaitworks in practice - How decorators work internally
- How to handle Google's thought signatures in function calling
- How
RotatingFileHandlerworks for log rotation - How
pathlibmakes file handling cross-platform - How context managers work (
__enter__and__exit__) - How chainable methods work (
return self) - How semantic versioning works in practice
- How to write 71 unit tests and fix every failure
- How to stay motivated when you want to give up
Try It Out!
pip install dracula-ai
If you find it useful, a ⭐ on GitHub would mean the world to me
as a CS student!
I'd love to hear your feedback, suggestions, or criticism in the
comments. What features would you like to see next? 🧛
Top comments (0)