This document provides comprehensive information about developing, building, and contributing to AI SDK C++.
- C++ Compiler: C++20 compatible compiler (GCC 10+, Clang 12+, MSVC 2019+)
- CMake: Version 3.16 or higher
- Python: 3.8+ with uv package manager for development scripts
- Git: For version control
- clang-format: For code formatting
- clang-tidy: For static analysis and linting
- gdb/lldb: For debugging
git clone --recursive https://github.com/ClickHouse/ai-sdk-cpp.git
cd ai-sdk-cppWe use uv for managing Python development tools:
# Install uv if you haven't already
curl -LsSf https://astral.sh/uv/install.sh | sh
# Development scripts will automatically install their dependencies
# when run with 'uv run'We provide a modern Python-based build script that handles all CMake configuration and building.
# Debug build (default)
uv run scripts/build.py
# Or explicitly specify debug mode
uv run scripts/build.py --mode debuguv run scripts/build.py --mode releaseuv run scripts/build.py --mode debug --testsuv run scripts/build.py --mode release --cleanuv run scripts/build.py --mode debug --export-compile-commandsThis generates compile_commands.json in both the build directory and project root, enabling excellent IDE support in VS Code, CLion, and other editors.
# Custom parallel jobs
uv run scripts/build.py --mode release --jobs 8
# Verbose output
uv run scripts/build.py --mode debug --verbose
# All options combined
uv run scripts/build.py --mode release --tests --clean --export-compile-commands --jobs 12If you prefer using CMake directly:
# Configure
cmake -B build -DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTS=ON \
-DBUILD_EXAMPLES=ON \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# Build
cmake --build build --parallel $(nproc)Note: Dependencies will be automatically fetched and built using CPM.cmake during the configuration step.
We provide several Python scripts to streamline development. All scripts use modern CLI tools (Click) and rich terminal output.
Location: scripts/build.py
Purpose: Build the project with CMake
# Show all options
uv run scripts/build.py --help
# Common usage patterns
uv run scripts/build.py --mode debug # Debug build
uv run scripts/build.py --mode release --tests # Release with tests
uv run scripts/build.py --clean --export-compile-commands # Clean + IDE supportFeatures:
- ✅ Debug/Release builds
- ✅ Test building toggle
- ✅ Clean builds
- ✅ Export compile commands for IDEs
- ✅ Parallel building
- ✅ Automatic dependency management via CPM.cmake
- ✅ Rich terminal output with progress indicators
Location: scripts/format.py
Purpose: Format C++ code using clang-format
# Format all files
uv run scripts/format.py
# Check formatting without modifying files
uv run scripts/format.py --checkFeatures:
- ✅ Formats all C++ files (
.h,.hpp,.cc,.cpp,.cxx) - ✅ Excludes build directories and dependency files
- ✅ Check mode for CI/CD
- ✅ Progress indicators
Location: scripts/lint.py
Purpose: Run clang-tidy for static analysis and linting
# Lint all files
uv run scripts/lint.py
# Auto-fix issues where possible
uv run scripts/lint.py --fix
# Custom parallel jobs
uv run scripts/lint.py --jobs 8Features:
- ✅ Parallel linting for faster execution
- ✅ Auto-fix capability
- ✅ Requires
compile_commands.json(build with--export-compile-commands) - ✅ macOS system include path handling
- ✅ Rich progress reporting
After building with tests enabled:
cd build
# Run all tests
ctest
# Verbose output
ctest --verbose
# Run specific test
ctest -R "test_types"
# Parallel test execution
ctest --parallel $(nproc)Tests use Google Test framework, which is automatically fetched when you build with tests enabled:
uv run scripts/build.py --testsThe build system will automatically download and configure Google Test via CPM.cmake.
Create test files in the tests/ directory following the pattern:
#include <gtest/gtest.h>
#include <ai/types.h>
TEST(TypesTest, BasicFunctionality) {
// Your test code here
EXPECT_EQ(expected, actual);
}We use clang-format with Google C++ style. Key guidelines:
- Indentation: 2 spaces
- Line Length: 80 characters
- Naming: CamelCase for types, snake_case for variables/functions
- Headers: Include guards and proper ordering
We use clang-tidy with these key checks:
- Modernize: Use modern C++ features (auto, nullptr, etc.)
- Performance: Avoid unnecessary copies, use move semantics
- Readability: Clear naming and structure
- Safety: Avoid common pitfalls and unsafe patterns
Before committing code:
# 1. Format code
uv run scripts/format.py
# 2. Build and test
uv run scripts/build.py --mode debug --tests
# 3. Run linting
uv run scripts/build.py --export-compile-commands # Ensure compile_commands.json exists
uv run scripts/lint.py
# 4. Run tests
cd build && ctestAI SDK C++ uses a built-in logging system defined in ai/logger.h.
You can control the logging verbosity by configuring the logger in your application:
#include "ai/logger.h"
// In your main() or initialization code:
// Enable debug logging (most verbose)
ai::logger::install_logger(
std::make_shared<ai::logger::ConsoleLogger>(ai::logger::LogLevel::kLogLevelDebug)
);
// Enable info logging (operational information)
ai::logger::install_logger(
std::make_shared<ai::logger::ConsoleLogger>(ai::logger::LogLevel::kLogLevelInfo)
);
// Enable warning logging (default)
ai::logger::install_logger(
std::make_shared<ai::logger::ConsoleLogger>(ai::logger::LogLevel::kLogLevelWarn)
);
// Enable error logging only
ai::logger::install_logger(
std::make_shared<ai::logger::ConsoleLogger>(ai::logger::LogLevel::kLogLevelError)
);From most to least verbose:
- debug: Detailed flow information, request/response bodies, connection details
- info: Important operational events (successful completions, stream events)
- warn: Warning conditions that don't prevent operation
- error: Error conditions and exceptions
To disable all logging, you can install a null logger:
ai::logger::install_logger(
std::make_shared<ai::logger::NullLogger>()
);With debug level enabled:
[2024-01-01 12:00:00.123] [debug] Initializing OpenAI client with base_url: https://api.openai.com
[2024-01-01 12:00:00.124] [debug] OpenAI client configured - host: api.openai.com, use_ssl: true
[2024-01-01 12:00:00.125] [debug] Starting text generation - model: gpt-4o, prompt length: 42
[2024-01-01 12:00:00.126] [debug] Request JSON built: {"model":"gpt-4o","messages":[...]}
[2024-01-01 12:00:00.127] [debug] Creating SSL client for host: api.openai.com
[2024-01-01 12:00:01.234] [debug] Received response - status: 200, body length: 1234
[2024-01-01 12:00:01.235] [info] Text generation successful - model: gpt-4o, response_id: chatcmpl-abc123
With info level enabled:
[2024-01-01 12:00:01.235] [info] Text generation successful - model: gpt-4o, response_id: chatcmpl-abc123
[2024-01-01 12:00:02.456] [info] Text streaming started - model: gpt-4o-mini
[2024-01-01 12:00:03.789] [info] Stream completed - tokens used: 150 prompt, 350 completion, 500 total
You can create your own logger by implementing the ai::logger::Logger interface:
#include "ai/logger.h"
#include <fstream>
class FileLogger : public ai::logger::Logger {
public:
FileLogger(const std::string& filename)
: file_(filename, std::ios::app) {}
void log(ai::logger::LogLevel level, std::string_view message) override {
if (is_enabled(level)) {
file_ << "[" << level_to_string(level) << "] " << message << std::endl;
}
}
bool is_enabled(ai::logger::LogLevel level) const override {
return level >= min_level_;
}
private:
std::ofstream file_;
ai::logger::LogLevel min_level_ = ai::logger::LogLevel::kLogLevelInfo;
static std::string_view level_to_string(ai::logger::LogLevel level) {
switch (level) {
case ai::logger::LogLevel::kLogLevelDebug: return "DEBUG";
case ai::logger::LogLevel::kLogLevelInfo: return "INFO";
case ai::logger::LogLevel::kLogLevelWarn: return "WARN";
case ai::logger::LogLevel::kLogLevelError: return "ERROR";
}
return "UNKNOWN";
}
};
// Install custom logger
ai::logger::install_logger(std::make_shared<FileLogger>("ai_sdk.log"));- Development: Use
debuglevel to see detailed API interactions - Testing: Use
infolevel to track key operations - Production: Use
warnorerrorlevel for performance
When running tests, you might want to enable debug logging:
# Run tests with debug logging
SPDLOG_LEVEL=debug ctest --verboseOr programmatically in your test fixtures:
class AITestFixture : public ::testing::Test {
protected:
void SetUp() override {
// Enable debug logging for tests
ai::logger::install_logger(
std::make_shared<ai::logger::ConsoleLogger>(ai::logger::LogLevel::kLogLevelDebug)
);
}
};Managed automatically via CPM.cmake:
- nlohmann-json: JSON parsing and generation (fetched from GitHub)
- cpp-httplib: HTTP client library with OpenSSL support (fetched from GitHub)
- concurrentqueue: Lock-free concurrent queue (fetched from GitHub)
- openssl: Cryptographic library (system-provided)
- Built-in logging: ai::logger provides flexible logging capabilities
Automatically fetched when tests are enabled:
- gtest: Google's C++ testing framework (fetched from GitHub via CPM.cmake)
Python tools (automatically managed by uv):
- click: Modern CLI framework
- rich: Rich terminal output
- asyncio: Async support for parallel operations
We follow the Google C++ Style Guide with these key principles:
- One class per file: Keep classes focused and maintainable
- Immutable by default: Prefer const and immutable designs
- Modern C++: Use C++20 features, auto, smart pointers
- RAII: Resource management through object lifetime
- Move semantics: Prefer move over copy for performance
- Use
autofor obvious types - Use
nullptrinstead ofNULLor0 - Prefer smart pointers (
std::unique_ptr,std::shared_ptr) over raw pointers - Use move semantics with
std::move - Mark functions
const,constexpr,noexceptwhen appropriate - Use brace initialization
{}instead of parentheses - Capture lambda variables explicitly, avoid
[=]and[&] - Use
std::atomicfor thread-safe operations, notvolatile - Prefer
emplaceoverinsert/push_back
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes following our code style
- Run the pre-commit workflow (format, build, test, lint)
- Commit with descriptive messages
- Push to your fork:
git push origin feature/amazing-feature - Create a Pull Request