Skip to content

Contributing to SelectoolsΒΆ

Thank you for your interest in contributing to Selectools! We welcome contributions from the community.

Current Version: v0.19.2 Test Status: 4612 tests passing (100%) Python: 3.9+

Getting StartedΒΆ

Development SetupΒΆ

  1. Fork and clone the repository
git clone https://github.com/johnnichev/selectools.git
cd selectools
  1. Create a virtual environment
python3 -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate
  1. Install in development mode
pip install -e .
pip install -e ".[dev]"  # Install dev dependencies
  1. Set up pre-commit hooks (recommended)

Pre-commit hooks automatically format code, check for issues, and ensure code quality before each commit:

# Install pre-commit hooks
pre-commit install

# Optionally run on all files to test
pre-commit run --all-files

The hooks will automatically run on staged files when you commit. They include: - Black - Code formatting - isort - Import sorting - flake8 - Linting - mypy - Type checking - bandit - Security checks

  1. Set up API keys for testing
# LLM Providers
export OPENAI_API_KEY="your-key-here"
export ANTHROPIC_API_KEY="your-key-here"  # Optional
export GEMINI_API_KEY="your-key-here"     # Optional

# Embedding Providers (for RAG features)
export VOYAGE_API_KEY="your-key-here"     # Optional (Anthropic embeddings)
export COHERE_API_KEY="your-key-here"     # Optional

# Vector Stores (for RAG features)
export PINECONE_API_KEY="your-key-here"   # Optional
export PINECONE_ENV="your-env-here"       # Optional

Available ScriptsΒΆ

Similar to npm run scripts, here are the common commands for this project:

TestingΒΆ

# Run all tests (4612 tests)
pytest tests/ -v

# Run tests quietly (summary only)
pytest tests/ -q

# Run specific test areas
pytest tests/agent/ -v             # Agent tests
pytest tests/rag/ -v               # RAG tests
pytest tests/tools/ -v             # Tool tests
pytest tests/core/ -v              # Core framework tests

# Run tests matching a pattern
pytest tests/ -k "test_tool" -v
pytest tests/ -k "rag" -v

# Run with coverage (if installed)
pytest tests/ --cov=src/selectools --cov-report=html

# Skip end-to-end tests (require real API keys)
pytest tests/ -k "not e2e" -v

Code QualityΒΆ

# Run all pre-commit hooks on all files
pre-commit run --all-files

# Run individual tools
black src/ tests/ examples/          # Format code
isort src/ tests/ examples/          # Sort imports
flake8 src/                          # Lint code
mypy src/                            # Type check
bandit -r src/                       # Security scan

ExamplesΒΆ

Examples are numbered by difficulty (see examples/ for the full list):

# Beginner β€” no API key needed
python examples/01_hello_world.py
python examples/02_search_weather.py
python examples/03_toolbox.py

# Intermediate β€” requires OPENAI_API_KEY
python examples/04_conversation_memory.py
python examples/09_caching.py
python examples/13_dynamic_tools.py

# RAG β€” requires OPENAI_API_KEY + selectools[rag]
python examples/14_rag_basic.py
python examples/18_hybrid_search.py
python examples/19_advanced_chunking.py

Development ScriptsΒΆ

# Quick smoke test for providers
python scripts/smoke_cli.py

# Test conversation memory with OpenAI
python scripts/test_memory_with_openai.py

ReleaseΒΆ

# Release a new version (recommended)
python scripts/release.py --version 0.5.1

# Dry run (see what would happen)
python scripts/release.py --version 0.5.1 --dry-run

# Or use the bash script
./scripts/release.sh 0.5.1

See scripts/README.md for detailed release instructions.

BuildingΒΆ

# Build the package
python -m build

# Check the built package
twine check dist/*

Running TestsΒΆ

Run the test suite to ensure everything works:

pytest tests/ -v

All tests should pass before submitting a pull request.

Code StyleΒΆ

We follow standard Python conventions and use automated tools to enforce consistency:

Style GuidelinesΒΆ

  • PEP 8 style guide (enforced by flake8)
  • Type hints for function signatures (checked by mypy)
  • Docstrings for public APIs (Google style preferred)
  • Clear variable names over abbreviations
  • Line length: 100 characters (configured in Black and isort)

Automated FormattingΒΆ

If you installed pre-commit hooks (recommended), your code will be automatically formatted on commit. You can also run formatters manually:

# Format code with Black
black src/ tests/ examples/

# Sort imports with isort
isort src/ tests/ examples/

# Check for linting issues
flake8 src/

# Type check
mypy src/

ExampleΒΆ

def execute_tool(tool: Tool, arguments: dict[str, Any]) -> str:
    """
    Execute a tool with the provided arguments.

    Args:
        tool: The tool to execute
        arguments: Dictionary of argument names to values

    Returns:
        The tool's output as a string

    Raises:
        ToolExecutionError: If the tool fails to execute
    """
    # Implementation here
    pass

Project StructureΒΆ

selectools/
β”œβ”€β”€ src/selectools/              # Main package
β”‚   β”œβ”€β”€ __init__.py             # Public exports
β”‚   β”œβ”€β”€ agent/                  # Agent loop and orchestration
β”‚   β”‚   β”œβ”€β”€ core.py             # Agent class
β”‚   β”‚   └── config.py           # AgentConfig
β”‚   β”œβ”€β”€ structured.py           # Structured output parsing & validation
β”‚   β”œβ”€β”€ trace.py                # AgentTrace & TraceStep
β”‚   β”œβ”€β”€ policy.py               # ToolPolicy (allow/review/deny)
β”‚   β”œβ”€β”€ env.py                  # Environment variable loading
β”‚   β”œβ”€β”€ exceptions.py           # Custom exception classes
β”‚   β”œβ”€β”€ memory.py               # ConversationMemory (tool-pair-aware)
β”‚   β”œβ”€β”€ models.py               # Model registry (152 models)
β”‚   β”œβ”€β”€ parser.py               # ToolCallParser
β”‚   β”œβ”€β”€ pricing.py              # LLM pricing data and cost calculation
β”‚   β”œβ”€β”€ prompt.py               # PromptBuilder
β”‚   β”œβ”€β”€ tools.py                # Tool, @tool, ToolRegistry
β”‚   β”œβ”€β”€ types.py                # Message, Role, StreamChunk, AgentResult
β”‚   β”œβ”€β”€ usage.py                # UsageTracker (tokens, costs, analytics)
β”‚   β”œβ”€β”€ cache.py                # InMemoryCache (LRU+TTL)
β”‚   β”œβ”€β”€ cache_redis.py          # RedisCache
β”‚   β”œβ”€β”€ providers/              # LLM provider adapters
β”‚   β”‚   β”œβ”€β”€ base.py             # Provider interface
β”‚   β”‚   β”œβ”€β”€ openai_provider.py  # OpenAI
β”‚   β”‚   β”œβ”€β”€ anthropic_provider.py # Anthropic
β”‚   β”‚   β”œβ”€β”€ gemini_provider.py  # Google Gemini
β”‚   β”‚   β”œβ”€β”€ ollama_provider.py  # Ollama local models
β”‚   β”‚   β”œβ”€β”€ fallback.py         # FallbackProvider (auto-failover)
β”‚   β”‚   └── stubs.py            # LocalProvider / test stubs
β”‚   β”œβ”€β”€ embeddings/             # Embedding providers
β”‚   β”œβ”€β”€ rag/                    # RAG: vector stores, chunking, loaders
β”‚   └── toolbox/                # 24 pre-built tools
β”œβ”€β”€ tests/                      # Test suite (4612 tests)
β”‚   β”œβ”€β”€ agent/                  # Agent tests
β”‚   β”œβ”€β”€ rag/                    # RAG tests
β”‚   β”œβ”€β”€ tools/                  # Tool tests
β”‚   β”œβ”€β”€ core/                   # Core framework tests
β”‚   └── integration/            # E2E tests (require API keys)
β”œβ”€β”€ examples/                   # 61 numbered examples (01–61)
β”œβ”€β”€ docs/                       # Detailed documentation
β”‚   β”œβ”€β”€ QUICKSTART.md           # 5-minute getting started
β”‚   β”œβ”€β”€ ARCHITECTURE.md         # Architecture overview
β”‚   └── modules/                # Per-module docs
└── scripts/                    # Release and dev scripts

How to ContributeΒΆ

Reporting BugsΒΆ

  1. Check if the bug has already been reported in Issues
  2. If not, create a new issue with:
  3. Clear description of the problem
  4. Steps to reproduce
  5. Expected vs actual behavior
  6. Python version and OS
  7. Relevant code snippets or error messages

Suggesting FeaturesΒΆ

  1. Check existing issues for similar suggestions
  2. Create a new issue describing:
  3. The problem you're trying to solve
  4. Your proposed solution
  5. Any alternatives you've considered
  6. Examples of how it would be used

Submitting Pull RequestsΒΆ

  1. Create a feature branch
git checkout -b feature/your-feature-name
# or
git checkout -b fix/your-bug-fix
  1. Make your changes
  2. Write clear, focused commits
  3. Add tests for new functionality
  4. Update documentation as needed

  5. Test your changes

python tests/test_framework.py
  1. Commit with clear messages
git commit -m "Add support for streaming tool results"

Good commit messages: - Use present tense ("Add feature" not "Added feature") - Be specific and descriptive - Reference issues when applicable (#123)

  1. Push and create a pull request
git push origin feature/your-feature-name

Then open a PR on GitHub with: - Clear description of changes - Link to related issues - Screenshots/examples if applicable

Areas for ContributionΒΆ

We especially welcome contributions in these areas:

πŸ”§ New ProvidersΒΆ

  • Add support for new LLM providers (Cohere, AI21, etc.)
  • Improve existing provider implementations
  • Add vision support to more providers
  • Add new embedding providers

πŸ› οΈ New ToolsΒΆ

  • Pre-built tools for common use cases
  • Integration with popular APIs and services
  • Example tools demonstrating best practices

πŸ—„οΈ RAG & Vector StoresΒΆ

  • Add new vector store integrations (Weaviate, Qdrant, Milvus)
  • Add new reranker integrations
  • Improve agentic chunking strategies
  • Performance benchmarks for different vector stores

πŸ“š DocumentationΒΆ

  • Improve README examples
  • Add tutorials and guides (especially for RAG features!)
  • Fix typos and clarify confusing sections
  • Add comparison guides (vs LangChain, LlamaIndex)

πŸ§ͺ TestingΒΆ

  • Increase test coverage (currently 4612 tests passing!)
  • Add performance benchmarks
  • Improve E2E test stability with retry/rate-limit handling

πŸ› Bug FixesΒΆ

  • Fix reported issues
  • Improve error messages
  • Handle edge cases

Adding a New ProviderΒΆ

To add support for a new LLM provider:

  1. Create a new provider file
# src/selectools/providers/your_provider.py

import os
from typing import Iterator

from .base import Provider
from ..exceptions import ProviderConfigurationError
from ..types import Message
from ..usage import UsageStats
from ..pricing import calculate_cost


class YourProvider(Provider):
    def __init__(self, api_key: str = None, default_model: str = "model-name"):
        self.api_key = api_key or os.getenv("YOUR_PROVIDER_API_KEY")
        if not self.api_key:
            raise ProviderConfigurationError(
                "API key is required",
                details={"env_var": "YOUR_PROVIDER_API_KEY"}
            )
        self.default_model = default_model

    def complete(
        self, messages: list[Message], model: str = None, **kwargs
    ) -> tuple[str, UsageStats]:
        model = model or self.default_model
        # Call your provider's API here
        # response = your_api_call(...)

        # Extract usage stats
        usage = UsageStats(
            prompt_tokens=response.usage.prompt_tokens,
            completion_tokens=response.usage.completion_tokens,
            model=model,
            provider="your_provider",
            cost_usd=calculate_cost(model, prompt_tokens, completion_tokens),
        )
        return response.text, usage

    def stream(
        self, messages: list[Message], model: str = None, **kwargs
    ) -> Iterator[str]:
        # Streaming only yields text chunks (no usage stats)
        for chunk in your_streaming_api_call(...):
            yield chunk.text
  1. Add tests
# tests/test_framework.py

def test_your_provider():
    # Add test cases
    pass
  1. Update documentation
  2. Add to README provider list
  3. Add usage example
  4. Update CHANGELOG

Adding a New ToolΒΆ

To contribute a new pre-built tool:

  1. Create the tool
# src/selectools/tools/your_tool.py

from ..tools import Tool, ToolParameter

def your_tool_implementation(param1: str, param2: int = 10) -> str:
    """Implementation of your tool."""
    # Your logic here
    return result

def create_your_tool() -> Tool:
    """Factory function to create the tool."""
    return Tool(
        name="your_tool",
        description="Clear description of what the tool does",
        parameters=[
            ToolParameter(name="param1", param_type=str, description="Description", required=True),
            ToolParameter(name="param2", param_type=int, description="Description", required=False),
        ],
        function=your_tool_implementation,
    )
  1. Add tests and examples

  2. Update documentation

Adding RAG Features (New in v0.8.0!)ΒΆ

Adding a New Vector StoreΒΆ

To add support for a new vector database:

  1. Create a new vector store implementation
# src/selectools/rag/stores/your_store.py

from typing import List, Optional
from ..vector_store import VectorStore, Document, SearchResult

class YourVectorStore(VectorStore):
    """Your vector database implementation."""

    def __init__(self, embedder, **config):
        self.embedder = embedder
        # Initialize your vector DB client
        # self.client = your_db.Client(**config)

    def add_documents(
        self,
        documents: List[Document],
        embeddings: Optional[List[List[float]]] = None
    ) -> List[str]:
        """Add documents to the store."""
        # Generate embeddings if not provided
        if embeddings is None:
            texts = [doc.text for doc in documents]
            embeddings = self.embedder.embed_texts(texts)

        # Insert into your vector DB
        # ids = self.client.insert(...)
        return ids

    def search(
        self,
        query_embedding: List[float],
        top_k: int = 5,
        filter: Optional[dict] = None
    ) -> List[SearchResult]:
        """Search for similar documents."""
        # results = self.client.search(query_embedding, top_k, filter)
        # return [SearchResult(document=..., score=...) for r in results]
        pass

    def delete(self, ids: List[str]) -> None:
        """Delete documents by ID."""
        # self.client.delete(ids)
        pass
  1. Register in the factory
# src/selectools/rag/vector_store.py

@staticmethod
def create(store_type: str, embedder, **kwargs) -> "VectorStore":
    if store_type == "your_store":
        from .stores.your_store import YourVectorStore
        return YourVectorStore(embedder, **kwargs)
    # ... existing stores
  1. Add tests
# tests/test_vector_stores_crud.py

class TestYourVectorStore:
    def test_add_and_search(self, mock_embedder):
        store = VectorStore.create("your_store", embedder=mock_embedder)
        docs = [Document(text="test", metadata={})]
        ids = store.add_documents(docs)

        query_emb = mock_embedder.embed_query("test")
        results = store.search(query_emb, top_k=1)

        assert len(results) == 1
        assert results[0].document.text == "test"

Adding a New Embedding ProviderΒΆ

  1. Create the provider
# src/selectools/embeddings/your_provider.py

from typing import List
from .provider import EmbeddingProvider

class YourEmbeddingProvider(EmbeddingProvider):
    def __init__(self, model: str = "default-model", api_key: str = None):
        self.model = model
        self.api_key = api_key or os.getenv("YOUR_API_KEY")
        # self.client = your_sdk.Client(api_key=self.api_key)

    def embed_texts(self, texts: List[str]) -> List[List[float]]:
        """Embed multiple texts."""
        # response = self.client.embeddings.create(input=texts, model=self.model)
        # return [emb.embedding for emb in response.data]
        pass

    def embed_query(self, query: str) -> List[float]:
        """Embed a single query."""
        return self.embed_texts([query])[0]
  1. Add model definitions to models.py

  2. Add tests and update documentation

Code Review ProcessΒΆ

  1. A maintainer will review your PR
  2. They may request changes or ask questions
  3. Once approved, your PR will be merged
  4. Your contribution will be included in the next release!

Questions?ΒΆ

  • Open an issue for questions about contributing
  • Check existing issues and PRs for similar discussions
  • Be patient and respectfulβ€”we're all volunteers!

LicenseΒΆ

By contributing to Selectools, you agree that your contributions will be licensed under the Apache-2.0 license.


Thank you for contributing to Selectools! πŸŽ‰