Python client library for the Keboola AI Assistant Backend API
Project description
Kai Client
A Python client library for interacting with the Keboola AI Assistant Backend API. This library provides async support, SSE streaming, and comprehensive type safety through Pydantic models.
Features
- Async/await support using
httpx - Server-Sent Events (SSE) streaming for real-time chat responses
- Type-safe models with Pydantic v2
- Comprehensive error handling with custom exception classes
- Session management for chat conversations
- Full API coverage including chat, history, and voting endpoints
Installation
Using uv (recommended)
uv add kai-client
Using pip
pip install kai-client
From source
git clone https://github.com/keboola/kai-client.git
cd kai-client
uv sync
Quick Start
import asyncio
from kai_client import KaiClient
async def main():
# Production: Auto-discover the kai-assistant URL from your Keboola stack
client = await KaiClient.from_storage_api(
storage_api_token="your-keboola-token",
storage_api_url="https://connection.keboola.com" # Your stack URL
)
async with client:
# Check server health
ping = await client.ping()
print(f"Server time: {ping.timestamp}")
# Start a new chat
chat_id = client.new_chat_id()
# Send a message and stream the response
async for event in client.send_message(chat_id, "What can you help me with?"):
if event.type == "text":
print(event.text, end="", flush=True)
elif event.type == "tool-call":
print(f"\n[Calling tool: {event.tool_name}]")
elif event.type == "finish":
print(f"\n[Finished: {event.finish_reason}]")
asyncio.run(main())
Local Development vs Production
| Setting | Local Dev | Production |
|---|---|---|
| Base URL | http://localhost:3000 |
Auto-discovered |
| Setup | Manual base_url parameter |
Use from_storage_api() |
# Local development (explicit base_url)
client = KaiClient(
storage_api_token="your-token",
storage_api_url="https://connection.keboola.com",
base_url="http://localhost:3000"
)
# Production (auto-discovers kai-assistant URL)
client = await KaiClient.from_storage_api(
storage_api_token="your-token",
storage_api_url="https://connection.keboola.com"
)
Usage Examples
Simple Chat (Non-Streaming)
async with KaiClient(
storage_api_token="your-token",
storage_api_url="https://connection.keboola.com"
) as client:
# Simple one-shot conversation
chat_id, response = await client.chat("What is 2 + 2?")
print(response)
Continuing a Conversation
async with KaiClient(
storage_api_token="your-token",
storage_api_url="https://connection.keboola.com"
) as client:
# Create a chat session
chat_id = client.new_chat_id()
# First message
async for event in client.send_message(chat_id, "Hello!"):
if event.type == "text":
print(event.text, end="")
print()
# Continue the conversation (reuse same chat_id)
async for event in client.send_message(chat_id, "What did I just say?"):
if event.type == "text":
print(event.text, end="")
print()
Using Different Models
from kai_client import KaiClient, ChatModel
async with KaiClient(
storage_api_token="your-token",
storage_api_url="https://connection.keboola.com"
) as client:
chat_id = client.new_chat_id()
# Use the reasoning model for complex tasks
async for event in client.send_message(
chat_id,
"Solve this step by step: If a train travels 120km in 2 hours...",
model=ChatModel.REASONING
):
if event.type == "text":
print(event.text, end="")
Handling Tool Calls
async with KaiClient(
storage_api_token="your-token",
storage_api_url="https://connection.keboola.com"
) as client:
chat_id = client.new_chat_id()
async for event in client.send_message(chat_id, "List my Keboola tables"):
match event.type:
case "text":
print(event.text, end="")
case "step-start":
print("\n--- New step ---")
case "tool-call":
if event.state == "input-available":
print(f"\n[Calling {event.tool_name} with {event.input}]")
elif event.state == "output-available":
print(f"\n[{event.tool_name} returned: {event.output}]")
case "finish":
print(f"\n[Done: {event.finish_reason}]")
Chat History
async with KaiClient(
storage_api_token="your-token",
storage_api_url="https://connection.keboola.com"
) as client:
# Get recent chats
history = await client.get_history(limit=20)
for chat in history.chats:
print(f"Chat {chat.id}: {chat.title}")
# Iterate through all history
async for chat in client.get_all_history():
print(f"Chat: {chat.title}")
# Get full chat details with messages
chat_detail = await client.get_chat(chat_id="some-chat-id")
for message in chat_detail.messages:
print(f"{message.role}: {message.parts}")
Voting on Messages
async with KaiClient(
storage_api_token="your-token",
storage_api_url="https://connection.keboola.com"
) as client:
# Upvote a helpful response
await client.upvote(chat_id="chat-uuid", message_id="message-uuid")
# Or downvote
await client.downvote(chat_id="chat-uuid", message_id="message-uuid")
# Get all votes for a chat
votes = await client.get_votes(chat_id="chat-uuid")
Tool Approval for Write Operations
Some tools (like create_config, run_job, create_flow) require explicit approval before execution:
async with KaiClient(
storage_api_token="your-token",
storage_api_url="https://connection.keboola.com"
) as client:
chat_id = client.new_chat_id()
pending_approval = None
async for event in client.send_message(chat_id, "Create a new bucket"):
if event.type == "text":
print(event.text, end="")
elif event.type == "tool-call":
if event.state == "input-available":
# Tool is waiting for approval
print(f"\nTool {event.tool_name} needs approval")
pending_approval = event
elif event.state == "output-available":
print(f"\nTool {event.tool_name} completed")
# Approve the pending tool call
if pending_approval:
async for event in client.confirm_tool(
chat_id=chat_id,
tool_call_id=pending_approval.tool_call_id,
tool_name=pending_approval.tool_name,
):
if event.type == "text":
print(event.text, end="")
# Or deny it
# async for event in client.deny_tool(chat_id, tool_call_id, tool_name):
# ...
Using SSE Stream Parser
from kai_client import KaiClient, SSEStreamParser
async with KaiClient(
storage_api_token="your-token",
storage_api_url="https://connection.keboola.com"
) as client:
parser = SSEStreamParser()
chat_id = client.new_chat_id()
async for event in client.send_message(chat_id, "Hello!"):
parser.process_event(event)
# Access accumulated data
print(f"Full response: {parser.text}")
print(f"Tool calls: {parser.tool_calls}")
print(f"Finished: {parser.finished}")
Error Handling
from kai_client import (
KaiClient,
KaiError,
KaiAuthenticationError,
KaiRateLimitError,
KaiNotFoundError,
)
async with KaiClient(
storage_api_token="your-token",
storage_api_url="https://connection.keboola.com"
) as client:
try:
async for event in client.send_message("chat-id", "Hello"):
print(event)
except KaiAuthenticationError as e:
print(f"Authentication failed: {e}")
except KaiRateLimitError as e:
print(f"Rate limited, try again later: {e}")
except KaiNotFoundError as e:
print(f"Chat not found: {e}")
except KaiError as e:
print(f"API error: {e.code} - {e.message}")
API Reference
KaiClient
The main client class for interacting with the Kai API.
Factory Method (Recommended for Production)
client = await KaiClient.from_storage_api(
storage_api_token: str, # Keboola Storage API token
storage_api_url: str, # Keboola connection URL (e.g., https://connection.keboola.com)
timeout: float = 300.0, # Request timeout in seconds
stream_timeout: float = 600.0 # Streaming timeout in seconds
)
This method auto-discovers the kai-assistant service URL from your Keboola stack.
Constructor (For Local Development)
KaiClient(
storage_api_token: str, # Keboola Storage API token
storage_api_url: str, # Keboola connection URL
base_url: str = "http://localhost:3000", # Kai API base URL
timeout: float = 300.0, # Request timeout in seconds
stream_timeout: float = 600.0 # Streaming timeout in seconds
)
Methods
| Method | Description |
|---|---|
from_storage_api(...) |
[Class method] Create client with auto-discovered URL |
ping() |
Check server health |
info() |
Get server information |
send_message(chat_id, text, ...) |
Send a message and stream response |
send_tool_result(chat_id, tool_call_id, ...) |
Send tool approval/denial result |
confirm_tool(chat_id, tool_call_id, ...) |
Approve a pending tool call |
deny_tool(chat_id, tool_call_id, ...) |
Deny a pending tool call |
chat(text, ...) |
Simple non-streaming chat |
get_chat(chat_id) |
Get chat details with messages |
get_history(limit, ...) |
Get chat history |
get_all_history() |
Iterate through all history |
delete_chat(chat_id) |
Delete a chat |
vote(chat_id, message_id, type) |
Vote on a message |
upvote(chat_id, message_id) |
Upvote a message |
downvote(chat_id, message_id) |
Downvote a message |
get_votes(chat_id) |
Get votes for a chat |
SSE Event Types
| Event Type | Description | Fields |
|---|---|---|
text |
Text content | text, state |
step-start |
Processing step started | - |
tool-call |
Tool being called | tool_call_id, tool_name, state, input, output |
finish |
Stream completed | finish_reason |
error |
Error occurred | message, code |
Exceptions
| Exception | Error Code | Description |
|---|---|---|
KaiError |
- | Base exception |
KaiAuthenticationError |
unauthorized:chat |
Invalid credentials |
KaiForbiddenError |
forbidden:chat |
Access denied |
KaiNotFoundError |
not_found:chat |
Resource not found |
KaiRateLimitError |
rate_limit:chat |
Rate limit exceeded |
KaiBadRequestError |
bad_request:api |
Invalid request |
KaiStreamError |
- | SSE stream error |
KaiConnectionError |
- | Connection failed |
KaiTimeoutError |
- | Request timed out |
Development
Setup
# Clone the repository
git clone https://github.com/keboola/kai-client.git
cd kai-client
# Install with dev dependencies
uv sync --dev
# Run tests
uv run pytest
# Run linting
uv run ruff check .
Running Tests
# All tests
uv run pytest
# With coverage
uv run pytest --cov=kai_client
# Specific test file
uv run pytest tests/test_client.py
License
MIT License - see LICENSE for details.
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 kai_client-0.2.0.tar.gz.
File metadata
- Download URL: kai_client-0.2.0.tar.gz
- Upload date:
- Size: 75.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b324e58699259996a48d86829c35f905bcc397a9e716d8a2e4816725ea87c827
|
|
| MD5 |
758dd6607c4b465b563c595c7562b817
|
|
| BLAKE2b-256 |
08871c546cba374c605d6cb3b02fe1720fc40a584bb4a18533ab0af4356fbd2a
|
Provenance
The following attestation bundles were made for kai_client-0.2.0.tar.gz:
Publisher:
publish.yml on jordanrburger/kai-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kai_client-0.2.0.tar.gz -
Subject digest:
b324e58699259996a48d86829c35f905bcc397a9e716d8a2e4816725ea87c827 - Sigstore transparency entry: 779556899
- Sigstore integration time:
-
Permalink:
jordanrburger/kai-client@d51ebb65cdcd83fc7b296c52c2826e06e547b5e8 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jordanrburger
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d51ebb65cdcd83fc7b296c52c2826e06e547b5e8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file kai_client-0.2.0-py3-none-any.whl.
File metadata
- Download URL: kai_client-0.2.0-py3-none-any.whl
- Upload date:
- Size: 18.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
402802d5f231687e97a047b8ab1d9f3f1eae38605fcb52818fcf84949cd7a093
|
|
| MD5 |
89fb349adf4334a7eb8d00ff1775c129
|
|
| BLAKE2b-256 |
0e04f257e2b8b205950a2ff41f135c9fa5555c2bfddc6830ca93574a00213549
|
Provenance
The following attestation bundles were made for kai_client-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on jordanrburger/kai-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kai_client-0.2.0-py3-none-any.whl -
Subject digest:
402802d5f231687e97a047b8ab1d9f3f1eae38605fcb52818fcf84949cd7a093 - Sigstore transparency entry: 779556904
- Sigstore integration time:
-
Permalink:
jordanrburger/kai-client@d51ebb65cdcd83fc7b296c52c2826e06e547b5e8 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jordanrburger
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d51ebb65cdcd83fc7b296c52c2826e06e547b5e8 -
Trigger Event:
release
-
Statement type: