diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 0000000..5970b57 --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,187 @@ +name: Publish Packages + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 0.1.13)' + required: true + default: '0.1.13' + +jobs: + publish: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.12'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Full history for version detection + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install build twine + make install-deps + + - name: Get version from tag or input + id: get-version + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + # Extract version from git tag + VERSION=${GITHUB_REF#refs/tags/} + echo "version=${VERSION}" >> $GITHUB_OUTPUT + else + # Use input version for manual dispatch + VERSION="${{ github.event.inputs.version }}" + echo "version=${VERSION}" >> $GITHUB_OUTPUT + fi + echo "Using version: ${VERSION}" + + - name: Update version in pyproject.toml + run: | + VERSION="${{ steps.get-version.outputs.version }}" + # Remove 'v' prefix if present + VERSION=${VERSION#v} + echo "Setting version to: ${VERSION}" + + # Update version in main pyproject.toml + sed -i "s/version = \".*\"/version = \"${VERSION}\"/" pyproject.toml + + # Show the updated file + echo "Updated pyproject.toml:" + cat pyproject.toml + + - name: Build all packages + run: | + echo "Building all packages..." + make build-all + + # List built packages + echo "Built packages:" + find providers -name "*.whl" -o -name "*.tar.gz" | sort + + - name: Test built packages + run: | + echo "Testing built packages..." + make test-build + + - name: Verify package structure + run: | + echo "Verifying package structure..." + + # Check that all expected packages were built + EXPECTED_PACKAGES=( + "lightspeed-inline-agent" + "lightspeed-agent" + "lightspeed-question-validity" + "lightspeed-redaction" + "lightspeed-tool-runtime" + ) + + for package in "${EXPECTED_PACKAGES[@]}"; do + echo "Checking package: ${package}" + + # Find the package directory + package_dir=$(find providers -name "${package}" -type d | head -1) + if [ -z "$package_dir" ]; then + echo "❌ Package directory not found: ${package}" + exit 1 + fi + + # Check for dist directory + if [ ! -d "${package_dir}/dist" ]; then + echo "❌ No dist directory found for: ${package}" + exit 1 + fi + + # Check for wheel and source distribution + wheel_count=$(find "${package_dir}/dist" -name "*.whl" | wc -l) + source_count=$(find "${package_dir}/dist" -name "*.tar.gz" | wc -l) + + if [ "$wheel_count" -eq 0 ] || [ "$source_count" -eq 0 ]; then + echo "❌ Missing wheel or source distribution for: ${package}" + exit 1 + fi + + echo "✅ Package ${package} verified" + done + + echo "✅ All packages verified successfully" + + - name: Publish to PyPI + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + echo "Publishing packages to PyPI..." + + # Publish all packages + make publish-all + + echo "✅ All packages published successfully" + + - name: Create GitHub Release + if: startsWith(github.ref, 'refs/tags/') + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body: | + ## Release ${{ github.ref }} + + ### Packages Published + + - **lightspeed-inline-agent** - Inline agent provider for llama-stack + - **lightspeed-agent** - Remote agent provider for llama-stack + - **lightspeed-question-validity** - Safety provider for content validation + - **lightspeed-redaction** - Safety provider for content redaction + - **lightspeed-tool-runtime** - Tool runtime provider for MCP integration + + ### Installation + + ```bash + # Install individual packages + pip install lightspeed-inline-agent + pip install lightspeed-agent + pip install lightspeed-question-validity + pip install lightspeed-redaction + pip install lightspeed-tool-runtime + ``` + + ### Changes + + See the commit history for detailed changes. + draft: false + prerelease: false + + - name: Notify on success + if: success() + run: | + echo "🎉 Release completed successfully!" + echo "Version: ${{ steps.get-version.outputs.version }}" + echo "All packages published to PyPI" + + - name: Notify on failure + if: failure() + run: | + echo "❌ Release failed!" + echo "Please check the logs for details" \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..383431b --- /dev/null +++ b/Makefile @@ -0,0 +1,150 @@ +# Makefile for building and publishing Lightspeed provider packages + +.PHONY: help build-all build-package publish-all publish-package clean test install-deps + +# Default target +help: + @echo "Available targets:" + @echo " build-all - Build all provider packages" + @echo " build-package - Build a specific package (use PACKAGE=name)" + @echo " publish-all - Build and publish all packages to PyPI" + @echo " publish-package - Build and publish specific package (use PACKAGE=name)" + @echo " clean - Clean build artifacts" + @echo " test - Run tests" + @echo " install-deps - Install development dependencies" + @echo " generate-template - Generate new provider template (use PROVIDER_NAME=name PROVIDER_TYPE=type)" + @echo "" + @echo "Available packages:" + @echo " lightspeed-inline-agent" + @echo " lightspeed-agent" + @echo " lightspeed-tool-runtime" + @echo " lightspeed-question-validity" + @echo " lightspeed-redaction" + @echo "" + @echo "Available provider types:" + @echo " inline-agent - Inline agent provider" + @echo " remote-agent - Remote agent provider" + @echo " remote-tool-runtime - Remote tool runtime provider" + @echo " inline-safety - Inline safety provider" + @echo " inline-inference - Inline inference provider" + @echo " remote-inference - Remote inference provider" + @echo " inline-vector-io - Inline vector I/O provider" + @echo " remote-vector-io - Remote vector I/O provider" + @echo " inline-vector-dbs - Inline vector database provider" + @echo " remote-vector-dbs - Remote vector database provider" + @echo " inline-tool-groups - Inline tool groups provider" + @echo " remote-tool-groups - Remote tool groups provider" + +# Install development dependencies +install-deps: + pip install build twine + pip install pytest + +# Build all packages +build-all: + @echo "Building all provider packages..." + python scripts/build_reorganized.py + +# Build specific package +build-package: + @if [ -z "$(PACKAGE)" ]; then \ + echo "Error: PACKAGE variable not set. Usage: make build-package PACKAGE=package-name"; \ + exit 1; \ + fi + @echo "Building package: $(PACKAGE)" + python scripts/build_reorganized.py --provider $(PACKAGE) + +# Publish all packages to PyPI +publish-all: build-all + @echo "Publishing all packages to PyPI..." + @for package in providers/*/*; do \ + if [ -d "$$package" ] && [ -d "$$package/dist" ]; then \ + echo "Publishing $$(basename $$package)..."; \ + cd "$$package" && python -m twine upload dist/* && cd -; \ + fi; \ + done + +# Publish specific package to PyPI +publish-package: build-package + @if [ -z "$(PACKAGE)" ]; then \ + echo "Error: PACKAGE variable not set. Usage: make publish-package PACKAGE=package-name"; \ + exit 1; \ + fi + @echo "Publishing package: $(PACKAGE)" + @package_dir=$$(find providers -name "$(PACKAGE)" -type d | head -1); \ + if [ -n "$$package_dir" ] && [ -d "$$package_dir/dist" ]; then \ + cd "$$package_dir" && python -m twine upload dist/* && cd -; \ + else \ + echo "Package not built yet. Run 'make build-package PACKAGE=$(PACKAGE)' first."; \ + exit 1; \ + fi + +# Clean build artifacts +clean: + @echo "Cleaning build artifacts..." + rm -rf dist/ + rm -rf build/ + rm -rf *.egg-info/ + find . -name "*.pyc" -delete + find . -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true + find providers -name "dist" -type d -exec rm -rf {} + 2>/dev/null || true + find providers -name "build" -type d -exec rm -rf {} + 2>/dev/null || true + find providers -name "*.egg-info" -type d -exec rm -rf {} + 2>/dev/null || true + +# Run tests +test: + @echo "Running tests..." + python -m pytest tests/ -v + +# Version management +version: + @echo "Current version: $$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")") + +# Update version (usage: make bump-version VERSION=1.2.3) +bump-version: + @if [ -z "$(VERSION)" ]; then \ + echo "Error: VERSION variable not set. Usage: make bump-version VERSION=1.2.3"; \ + exit 1; \ + fi + @echo "Updating version to $(VERSION)..." + sed -i.bak 's/version = ".*"/version = "$(VERSION)"/' pyproject.toml + rm pyproject.toml.bak + @echo "Version updated to $(VERSION)" + +# Build and test all packages +test-build: build-all + @echo "Testing built packages..." + @for package in providers/*/*; do \ + if [ -d "$$package" ] && [ -d "$$package/dist" ]; then \ + echo "Testing $$(basename $$package)..."; \ + cd "$$package" && python -m pip install dist/*.whl --force-reinstall && cd -; \ + fi; \ + done + +# Show package info +package-info: + @if [ -z "$(PACKAGE)" ]; then \ + echo "Error: PACKAGE variable not set. Usage: make package-info PACKAGE=package-name"; \ + exit 1; \ + fi + @echo "Package info for $(PACKAGE):" + @package_dir=$$(find providers -name "$(PACKAGE)" -type d | head -1); \ + if [ -n "$$package_dir" ] && [ -f "$$package_dir/pyproject.toml" ]; then \ + echo "pyproject.toml:"; \ + cat "$$package_dir/pyproject.toml"; \ + else \ + echo "Package not found or not built yet. Run 'make build-package PACKAGE=$(PACKAGE)' first."; \ + fi + +# Generate new provider template +generate-template: + @if [ -z "$(PROVIDER_NAME)" ]; then \ + echo "Error: PROVIDER_NAME variable not set. Usage: make generate-template PROVIDER_NAME=my-provider PROVIDER_TYPE=inline-agent"; \ + exit 1; \ + fi + @if [ -z "$(PROVIDER_TYPE)" ]; then \ + echo "Error: PROVIDER_TYPE variable not set. Usage: make generate-template PROVIDER_NAME=my-provider PROVIDER_TYPE=inline-agent"; \ + exit 1; \ + fi + @echo "Generating template for $(PROVIDER_NAME) ($(PROVIDER_TYPE))..." + python scripts/generate_provider_template.py $(PROVIDER_NAME) $(PROVIDER_TYPE) \ No newline at end of file diff --git a/README.md b/README.md index 75d6b62..0903518 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,191 @@ -# lightspeed-providers -Source code for our custom providers. +# Lightspeed Llama-Stack Providers -## Building and publishing +A collection of llama-stack external providers organized by type and published as individual PyPI packages. -Manual procedure, assuming an existing PyPI API token available: +## 📁 Repository Structure - ## Generate distribution archives to be uploaded into Python registry - pdm run python -m build - ## Upload distribution archives into Python registry - pdm run python -m twine upload --repository ${PYTHON_REGISTRY} dist/* +``` +providers/ +├── agents/ +│ ├── lightspeed-inline-agent/ # Inline agent with tool filtering +│ └── lightspeed-agent/ # Remote agent service +├── safety/ +│ ├── lightspeed-question-validity/ # Content validation +│ └── lightspeed-redaction/ # Content redaction +├── tool-runtime/ +│ └── lightspeed-tool-runtime/ # MCP-based tool execution +├── inference/ # Ready for future providers +├── vector-io/ # Ready for future providers +├── vector-dbs/ # Ready for future providers +└── tool-groups/ # Ready for future providers +``` + +## 🚀 Quick Start + +### Install Individual Packages + +```bash +# Agent providers +pip install lightspeed-inline-agent +pip install lightspeed-agent + +# Safety providers +pip install lightspeed-question-validity +pip install lightspeed-redaction + +# Tool runtime provider +pip install lightspeed-tool-runtime +``` + +### Build All Packages + +```bash +# Build all packages +make build-all + +# Build specific package +make build-package PACKAGE=lightspeed-inline-agent +``` + +## 🔧 Development + +### Local Development + +```bash +# Clone the repository +git clone +cd lightspeed-providers + +# Install development dependencies +make install-deps + +# Build all packages +make build-all + +# Test built packages +make test-build +``` + +### Adding New Providers + +```bash +# Generate template +make generate-template PROVIDER_NAME=my-provider PROVIDER_TYPE=inline-agent + +# Build and test +make build-package PACKAGE=my-provider +make test-build +``` + +## 📦 Build and Release Automation + +### Automated Release Process + +1. **Update Version**: Edit version in `pyproject.toml` +2. **Create Tag**: `git tag v0.1.13 && git push origin v0.1.13` +3. **GitHub Actions**: Automatically builds, tests, and publishes to PyPI + +### Manual Release Process + +```bash +# Build all packages +make build-all + +# Test packages +make test-build + +# Publish to PyPI +make publish-all + +# Create git tag +git tag v0.1.13 +git push origin v0.1.13 +``` + +## 🔄 Version Management + +### Lockstep Versioning + +All providers use **lockstep versioning** - all packages share the same version from the main `pyproject.toml`: + +```toml +# pyproject.toml +version = "0.1.12" # Single source of truth +``` + +### Version Increment Process + +1. **Update Version**: Edit version in `pyproject.toml` +2. **Build**: `make build-all` (automatically uses new version) +3. **Release**: `git tag v0.1.13 && git push origin v0.1.13` + +## 🏗️ Build System + +### Makefile Targets + +```bash +# Build targets +make build-all # Build all packages +make build-package PACKAGE=name # Build specific package + +# Test targets +make test-build # Test all built packages +make test-package PACKAGE=name # Test specific package + +# Publish targets +make publish-all # Publish all packages to PyPI +make publish-package PACKAGE=name # Publish specific package + +# Development targets +make install-deps # Install development dependencies +make clean # Clean build artifacts +make generate-template # Generate new provider template +``` + +## 🔍 Provider Types + +### Inline vs Remote Providers + +**Inline Providers** (run in-process): +- `lightspeed-inline-agent` - Complex orchestration with multiple APIs +- `lightspeed-question-validity` - Real-time content validation +- `lightspeed-redaction` - Real-time content redaction + +**Remote Providers** (external services): +- `lightspeed-agent` - External agent service integration +- `lightspeed-tool-runtime` - MCP-based tool execution + +## 📋 Available Packages + +| Package | Type | Description | +|---------|------|-------------| +| `lightspeed-inline-agent` | Inline Agent | Agent orchestration with tool filtering | +| `lightspeed-agent` | Remote Agent | Remote agent service integration | +| `lightspeed-question-validity` | Inline Safety | Content validation and filtering | +| `lightspeed-redaction` | Inline Safety | Content redaction and safety | +| `lightspeed-tool-runtime` | Remote Tool Runtime | MCP-based tool execution | + +## 🤝 Contributing + +### Provider Implementation Guidelines + +**Inline Providers** use `get_provider_impl()` and run in-process: +```yaml +api: Api.agents +provider_type: inline::provider_name +config_class: package_name.config.ConfigClass +module: package_name +``` + +**Remote Providers** use `get_adapter_impl()` and run as external services: +```yaml +api: Api.agents +adapter: + adapter_type: provider_name + config_class: package_name.config.ConfigClass + module: package_name +``` + +## 📄 License + +MIT License - see LICENSE file for details. diff --git a/lightspeed_stack_providers/__init__.py b/lightspeed_stack_providers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lightspeed_stack_providers/providers/__init__.py b/lightspeed_stack_providers/providers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lightspeed_stack_providers/providers/inline/__init__.py b/lightspeed_stack_providers/providers/inline/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lightspeed_stack_providers/providers/inline/agents/__init__.py b/lightspeed_stack_providers/providers/inline/agents/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lightspeed_stack_providers/providers/inline/safety/__init__.py b/lightspeed_stack_providers/providers/inline/safety/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lightspeed_stack_providers/providers/remote/__init__.py b/lightspeed_stack_providers/providers/remote/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lightspeed_stack_providers/providers/remote/agents/__init__.py b/lightspeed_stack_providers/providers/remote/agents/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lightspeed_stack_providers/providers/remote/tool_runtime/__init__.py b/lightspeed_stack_providers/providers/remote/tool_runtime/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/providers/agents/lightspeed-agent/README.md b/providers/agents/lightspeed-agent/README.md new file mode 100644 index 0000000..332b0be --- /dev/null +++ b/providers/agents/lightspeed-agent/README.md @@ -0,0 +1,60 @@ +# lightspeed-agent + +Lightspeed remote agent provider for llama-stack. + +## Installation + +```bash +pip install lightspeed-agent +``` + +## Usage + +This provider is designed to work with llama-stack. + +### Configuration + +Add the provider configuration to your llama-stack configuration: + +```yaml +# Example configuration +providers: + lightspeed-agent: + enabled: true + api_key: "${env.LIGHTSPEED_AGENT_API_KEY}" + api_url: "${env.LIGHTSPEED_AGENT_API_URL:http://localhost:8080/agent}" +``` + +### Integration + +The provider will be automatically loaded by llama-stack when properly configured. + +## Development + +### Local Development + +```bash +# Clone the repository +git clone +cd lightspeed-providers/providers/agents/lightspeed-agent + +# Install in development mode +pip install -e . + +# Run tests +python -m pytest +``` + +### Building + +```bash +# Build the package +python -m build + +# Install from wheel +pip install dist/*.whl +``` + +## License + +MIT License - see LICENSE file for details. \ No newline at end of file diff --git a/resources/external_providers/remote/agents/lightspeed_agent.yaml b/providers/agents/lightspeed-agent/config/lightspeed_agent.yaml similarity index 100% rename from resources/external_providers/remote/agents/lightspeed_agent.yaml rename to providers/agents/lightspeed-agent/config/lightspeed_agent.yaml diff --git a/providers/agents/lightspeed-agent/pyproject.toml b/providers/agents/lightspeed-agent/pyproject.toml new file mode 100644 index 0000000..9a6ae8c --- /dev/null +++ b/providers/agents/lightspeed-agent/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "lightspeed-agent" +version = "0.1.12" +description = "Lightspeed remote agent provider for llama-stack" +readme = "README.md" +license = {text = "MIT"} +requires-python = ">=3.12" +authors = [ + {name = "Lightspeed Team"} +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "llama-stack==0.2.16", + "httpx", + "pydantic>=2.10.6", +] + +[project.urls] +Homepage = "https://github.com/your-org/lightspeed-providers" +Repository = "https://github.com/your-org/lightspeed-providers" +Issues = "https://github.com/your-org/lightspeed-providers/issues" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +"lightspeed_agent" = ["*.yaml", "*.yml"] + +[tool.setuptools.data-files] +config = ["config/*.yaml"] \ No newline at end of file diff --git a/providers/agents/lightspeed-agent/src/__init__.py b/providers/agents/lightspeed-agent/src/__init__.py new file mode 100644 index 0000000..3877b00 --- /dev/null +++ b/providers/agents/lightspeed-agent/src/__init__.py @@ -0,0 +1 @@ +# Package initialization \ No newline at end of file diff --git a/lightspeed_stack_providers/providers/remote/agents/lightspeed_agent/README.md b/providers/agents/lightspeed-agent/src/lightspeed_agent/README.md similarity index 100% rename from lightspeed_stack_providers/providers/remote/agents/lightspeed_agent/README.md rename to providers/agents/lightspeed-agent/src/lightspeed_agent/README.md diff --git a/lightspeed_stack_providers/providers/remote/agents/lightspeed_agent/__init__.py b/providers/agents/lightspeed-agent/src/lightspeed_agent/__init__.py similarity index 100% rename from lightspeed_stack_providers/providers/remote/agents/lightspeed_agent/__init__.py rename to providers/agents/lightspeed-agent/src/lightspeed_agent/__init__.py diff --git a/lightspeed_stack_providers/providers/remote/agents/lightspeed_agent/config.py b/providers/agents/lightspeed-agent/src/lightspeed_agent/config.py similarity index 100% rename from lightspeed_stack_providers/providers/remote/agents/lightspeed_agent/config.py rename to providers/agents/lightspeed-agent/src/lightspeed_agent/config.py diff --git a/lightspeed_stack_providers/providers/remote/agents/lightspeed_agent/lightspeed.py b/providers/agents/lightspeed-agent/src/lightspeed_agent/lightspeed.py similarity index 100% rename from lightspeed_stack_providers/providers/remote/agents/lightspeed_agent/lightspeed.py rename to providers/agents/lightspeed-agent/src/lightspeed_agent/lightspeed.py diff --git a/providers/agents/lightspeed-inline-agent/README.md b/providers/agents/lightspeed-inline-agent/README.md new file mode 100644 index 0000000..135a1e9 --- /dev/null +++ b/providers/agents/lightspeed-inline-agent/README.md @@ -0,0 +1,61 @@ +# lightspeed-inline-agent + +Lightspeed inline agent provider for llama-stack. + +## Installation + +```bash +pip install lightspeed-inline-agent +``` + +## Usage + +This provider is designed to work with llama-stack. + +### Configuration + +Add the provider configuration to your llama-stack configuration: + +```yaml +# Example configuration +providers: + lightspeed-inline-agent: + enabled: true + tools_filter: + model_id: "gpt-3.5-turbo" + enabled: true +``` + +### Integration + +The provider will be automatically loaded by llama-stack when properly configured. + +## Development + +### Local Development + +```bash +# Clone the repository +git clone +cd lightspeed-providers/providers/agents/lightspeed-inline-agent + +# Install in development mode +pip install -e . + +# Run tests +python -m pytest +``` + +### Building + +```bash +# Build the package +python -m build + +# Install from wheel +pip install dist/*.whl +``` + +## License + +MIT License - see LICENSE file for details. \ No newline at end of file diff --git a/resources/external_providers/inline/agents/lightspeed_inline_agent.yaml b/providers/agents/lightspeed-inline-agent/config/lightspeed_inline_agent.yaml similarity index 100% rename from resources/external_providers/inline/agents/lightspeed_inline_agent.yaml rename to providers/agents/lightspeed-inline-agent/config/lightspeed_inline_agent.yaml diff --git a/providers/agents/lightspeed-inline-agent/pyproject.toml b/providers/agents/lightspeed-inline-agent/pyproject.toml new file mode 100644 index 0000000..01e833c --- /dev/null +++ b/providers/agents/lightspeed-inline-agent/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "lightspeed-inline-agent" +version = "0.1.12" +description = "Lightspeed inline agent provider for llama-stack" +readme = "README.md" +license = {text = "MIT"} +requires-python = ">=3.12" +authors = [ + {name = "Lightspeed Team"} +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "llama-stack==0.2.16", + "httpx", + "pydantic>=2.10.6", +] + +[project.urls] +Homepage = "https://github.com/your-org/lightspeed-providers" +Repository = "https://github.com/your-org/lightspeed-providers" +Issues = "https://github.com/your-org/lightspeed-providers/issues" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +"lightspeed_inline_agent" = ["*.yaml", "*.yml"] + +[tool.setuptools.data-files] +config = ["config/*.yaml"] \ No newline at end of file diff --git a/providers/agents/lightspeed-inline-agent/src/__init__.py b/providers/agents/lightspeed-inline-agent/src/__init__.py new file mode 100644 index 0000000..3877b00 --- /dev/null +++ b/providers/agents/lightspeed-inline-agent/src/__init__.py @@ -0,0 +1 @@ +# Package initialization \ No newline at end of file diff --git a/lightspeed_stack_providers/providers/inline/agents/lightspeed_inline_agent/__init__.py b/providers/agents/lightspeed-inline-agent/src/lightspeed_inline_agent/__init__.py similarity index 100% rename from lightspeed_stack_providers/providers/inline/agents/lightspeed_inline_agent/__init__.py rename to providers/agents/lightspeed-inline-agent/src/lightspeed_inline_agent/__init__.py diff --git a/lightspeed_stack_providers/providers/inline/agents/lightspeed_inline_agent/agent_instance.py b/providers/agents/lightspeed-inline-agent/src/lightspeed_inline_agent/agent_instance.py similarity index 100% rename from lightspeed_stack_providers/providers/inline/agents/lightspeed_inline_agent/agent_instance.py rename to providers/agents/lightspeed-inline-agent/src/lightspeed_inline_agent/agent_instance.py diff --git a/lightspeed_stack_providers/providers/inline/agents/lightspeed_inline_agent/agents.py b/providers/agents/lightspeed-inline-agent/src/lightspeed_inline_agent/agents.py similarity index 100% rename from lightspeed_stack_providers/providers/inline/agents/lightspeed_inline_agent/agents.py rename to providers/agents/lightspeed-inline-agent/src/lightspeed_inline_agent/agents.py diff --git a/lightspeed_stack_providers/providers/inline/agents/lightspeed_inline_agent/config.py b/providers/agents/lightspeed-inline-agent/src/lightspeed_inline_agent/config.py similarity index 100% rename from lightspeed_stack_providers/providers/inline/agents/lightspeed_inline_agent/config.py rename to providers/agents/lightspeed-inline-agent/src/lightspeed_inline_agent/config.py diff --git a/providers/inference/.gitkeep b/providers/inference/.gitkeep new file mode 100644 index 0000000..9b76c1e --- /dev/null +++ b/providers/inference/.gitkeep @@ -0,0 +1,2 @@ +# This file ensures the inference directory is tracked by Git +# Future inference providers will be added here \ No newline at end of file diff --git a/providers/safety/lightspeed-question-validity/README.md b/providers/safety/lightspeed-question-validity/README.md new file mode 100644 index 0000000..575a087 --- /dev/null +++ b/providers/safety/lightspeed-question-validity/README.md @@ -0,0 +1,61 @@ +# lightspeed-question-validity + +Lightspeed question validity safety provider for llama-stack. + +## Installation + +```bash +pip install lightspeed-question-validity +``` + +## Usage + +This provider is designed to work with llama-stack. + +### Configuration + +Add the provider configuration to your llama-stack configuration: + +```yaml +# Example configuration +providers: + lightspeed-question-validity: + enabled: true + model_id: "gpt-3.5-turbo" + model_prompt: "Validate if this question is appropriate..." + invalid_question_response: "This question is not appropriate." +``` + +### Integration + +The provider will be automatically loaded by llama-stack when properly configured. + +## Development + +### Local Development + +```bash +# Clone the repository +git clone +cd lightspeed-providers/providers/safety/lightspeed-question-validity + +# Install in development mode +pip install -e . + +# Run tests +python -m pytest +``` + +### Building + +```bash +# Build the package +python -m build + +# Install from wheel +pip install dist/*.whl +``` + +## License + +MIT License - see LICENSE file for details. \ No newline at end of file diff --git a/resources/external_providers/inline/safety/lightspeed_question_validity.yaml b/providers/safety/lightspeed-question-validity/config/lightspeed_question_validity.yaml similarity index 100% rename from resources/external_providers/inline/safety/lightspeed_question_validity.yaml rename to providers/safety/lightspeed-question-validity/config/lightspeed_question_validity.yaml diff --git a/providers/safety/lightspeed-question-validity/pyproject.toml b/providers/safety/lightspeed-question-validity/pyproject.toml new file mode 100644 index 0000000..3cc9890 --- /dev/null +++ b/providers/safety/lightspeed-question-validity/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "lightspeed-question-validity" +version = "0.1.12" +description = "Lightspeed question validity safety provider for llama-stack" +readme = "README.md" +license = {text = "MIT"} +requires-python = ">=3.12" +authors = [ + {name = "Lightspeed Team"} +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "llama-stack==0.2.16", + "httpx", + "pydantic>=2.10.6", +] + +[project.urls] +Homepage = "https://github.com/your-org/lightspeed-providers" +Repository = "https://github.com/your-org/lightspeed-providers" +Issues = "https://github.com/your-org/lightspeed-providers/issues" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +"lightspeed_question_validity" = ["*.yaml", "*.yml"] + +[tool.setuptools.data-files] +config = ["config/*.yaml"] \ No newline at end of file diff --git a/providers/safety/lightspeed-question-validity/src/__init__.py b/providers/safety/lightspeed-question-validity/src/__init__.py new file mode 100644 index 0000000..3877b00 --- /dev/null +++ b/providers/safety/lightspeed-question-validity/src/__init__.py @@ -0,0 +1 @@ +# Package initialization \ No newline at end of file diff --git a/lightspeed_stack_providers/providers/inline/safety/lightspeed_question_validity/__init__.py b/providers/safety/lightspeed-question-validity/src/lightspeed_question_validity/__init__.py similarity index 100% rename from lightspeed_stack_providers/providers/inline/safety/lightspeed_question_validity/__init__.py rename to providers/safety/lightspeed-question-validity/src/lightspeed_question_validity/__init__.py diff --git a/lightspeed_stack_providers/providers/inline/safety/lightspeed_question_validity/config.py b/providers/safety/lightspeed-question-validity/src/lightspeed_question_validity/config.py similarity index 100% rename from lightspeed_stack_providers/providers/inline/safety/lightspeed_question_validity/config.py rename to providers/safety/lightspeed-question-validity/src/lightspeed_question_validity/config.py diff --git a/lightspeed_stack_providers/providers/inline/safety/lightspeed_question_validity/safety.py b/providers/safety/lightspeed-question-validity/src/lightspeed_question_validity/safety.py similarity index 100% rename from lightspeed_stack_providers/providers/inline/safety/lightspeed_question_validity/safety.py rename to providers/safety/lightspeed-question-validity/src/lightspeed_question_validity/safety.py diff --git a/providers/safety/lightspeed-redaction/README.md b/providers/safety/lightspeed-redaction/README.md new file mode 100644 index 0000000..e818cdb --- /dev/null +++ b/providers/safety/lightspeed-redaction/README.md @@ -0,0 +1,59 @@ +# lightspeed-redaction + +Lightspeed redaction safety provider for llama-stack. + +## Installation + +```bash +pip install lightspeed-redaction +``` + +## Usage + +This provider is designed to work with llama-stack. + +### Configuration + +Add the provider configuration to your llama-stack configuration: + +```yaml +# Example configuration +providers: + lightspeed-redaction: + enabled: true + # Add your redaction configuration here +``` + +### Integration + +The provider will be automatically loaded by llama-stack when properly configured. + +## Development + +### Local Development + +```bash +# Clone the repository +git clone +cd lightspeed-providers/providers/safety/lightspeed-redaction + +# Install in development mode +pip install -e . + +# Run tests +python -m pytest +``` + +### Building + +```bash +# Build the package +python -m build + +# Install from wheel +pip install dist/*.whl +``` + +## License + +MIT License - see LICENSE file for details. \ No newline at end of file diff --git a/resources/external_providers/inline/safety/lightspeed_redaction.yaml b/providers/safety/lightspeed-redaction/config/lightspeed_redaction.yaml similarity index 100% rename from resources/external_providers/inline/safety/lightspeed_redaction.yaml rename to providers/safety/lightspeed-redaction/config/lightspeed_redaction.yaml diff --git a/providers/safety/lightspeed-redaction/pyproject.toml b/providers/safety/lightspeed-redaction/pyproject.toml new file mode 100644 index 0000000..f0941a7 --- /dev/null +++ b/providers/safety/lightspeed-redaction/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "lightspeed-redaction" +version = "0.1.12" +description = "Lightspeed redaction safety provider for llama-stack" +readme = "README.md" +license = {text = "MIT"} +requires-python = ">=3.12" +authors = [ + {name = "Lightspeed Team"} +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "llama-stack==0.2.16", + "httpx", + "pydantic>=2.10.6", +] + +[project.urls] +Homepage = "https://github.com/your-org/lightspeed-providers" +Repository = "https://github.com/your-org/lightspeed-providers" +Issues = "https://github.com/your-org/lightspeed-providers/issues" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +"lightspeed_redaction" = ["*.yaml", "*.yml"] + +[tool.setuptools.data-files] +config = ["config/*.yaml"] \ No newline at end of file diff --git a/providers/safety/lightspeed-redaction/src/__init__.py b/providers/safety/lightspeed-redaction/src/__init__.py new file mode 100644 index 0000000..3877b00 --- /dev/null +++ b/providers/safety/lightspeed-redaction/src/__init__.py @@ -0,0 +1 @@ +# Package initialization \ No newline at end of file diff --git a/lightspeed_stack_providers/providers/inline/safety/lightspeed_redaction/lightspeed_redaction/__init__.py b/providers/safety/lightspeed-redaction/src/lightspeed_redaction/lightspeed_redaction/__init__.py similarity index 100% rename from lightspeed_stack_providers/providers/inline/safety/lightspeed_redaction/lightspeed_redaction/__init__.py rename to providers/safety/lightspeed-redaction/src/lightspeed_redaction/lightspeed_redaction/__init__.py diff --git a/lightspeed_stack_providers/providers/inline/safety/lightspeed_redaction/lightspeed_redaction/config.py b/providers/safety/lightspeed-redaction/src/lightspeed_redaction/lightspeed_redaction/config.py similarity index 100% rename from lightspeed_stack_providers/providers/inline/safety/lightspeed_redaction/lightspeed_redaction/config.py rename to providers/safety/lightspeed-redaction/src/lightspeed_redaction/lightspeed_redaction/config.py diff --git a/lightspeed_stack_providers/providers/inline/safety/lightspeed_redaction/lightspeed_redaction/redaction.py b/providers/safety/lightspeed-redaction/src/lightspeed_redaction/lightspeed_redaction/redaction.py similarity index 100% rename from lightspeed_stack_providers/providers/inline/safety/lightspeed_redaction/lightspeed_redaction/redaction.py rename to providers/safety/lightspeed-redaction/src/lightspeed_redaction/lightspeed_redaction/redaction.py diff --git a/providers/tool-groups/.gitkeep b/providers/tool-groups/.gitkeep new file mode 100644 index 0000000..78f95b1 --- /dev/null +++ b/providers/tool-groups/.gitkeep @@ -0,0 +1,2 @@ +# This file ensures the tool-groups directory is tracked by Git +# Future tool-groups providers will be added here \ No newline at end of file diff --git a/providers/tool-runtime/lightspeed-tool-runtime/README.md b/providers/tool-runtime/lightspeed-tool-runtime/README.md new file mode 100644 index 0000000..5582ebb --- /dev/null +++ b/providers/tool-runtime/lightspeed-tool-runtime/README.md @@ -0,0 +1,59 @@ +# lightspeed-tool-runtime + +Lightspeed tool runtime provider for llama-stack. + +## Installation + +```bash +pip install lightspeed-tool-runtime +``` + +## Usage + +This provider is designed to work with llama-stack. + +### Configuration + +Add the provider configuration to your llama-stack configuration: + +```yaml +# Example configuration +providers: + lightspeed-tool-runtime: + enabled: true + api_key: "${env.LIGHTSPEED_API_KEY}" +``` + +### Integration + +The provider will be automatically loaded by llama-stack when properly configured. + +## Development + +### Local Development + +```bash +# Clone the repository +git clone +cd lightspeed-providers/providers/tool-runtime/lightspeed-tool-runtime + +# Install in development mode +pip install -e . + +# Run tests +python -m pytest +``` + +### Building + +```bash +# Build the package +python -m build + +# Install from wheel +pip install dist/*.whl +``` + +## License + +MIT License - see LICENSE file for details. \ No newline at end of file diff --git a/resources/external_providers/remote/tool_runtime/lightspeed.yaml b/providers/tool-runtime/lightspeed-tool-runtime/config/lightspeed.yaml similarity index 100% rename from resources/external_providers/remote/tool_runtime/lightspeed.yaml rename to providers/tool-runtime/lightspeed-tool-runtime/config/lightspeed.yaml diff --git a/providers/tool-runtime/lightspeed-tool-runtime/pyproject.toml b/providers/tool-runtime/lightspeed-tool-runtime/pyproject.toml new file mode 100644 index 0000000..f66a5ee --- /dev/null +++ b/providers/tool-runtime/lightspeed-tool-runtime/pyproject.toml @@ -0,0 +1,41 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "lightspeed-tool-runtime" +version = "0.1.12" +description = "Lightspeed tool runtime provider for llama-stack" +readme = "README.md" +license = {text = "MIT"} +requires-python = ">=3.12" +authors = [ + {name = "Lightspeed Team"} +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "llama-stack==0.2.16", + "httpx", + "pydantic>=2.10.6", + "mcp", +] + +[project.urls] +Homepage = "https://github.com/your-org/lightspeed-providers" +Repository = "https://github.com/your-org/lightspeed-providers" +Issues = "https://github.com/your-org/lightspeed-providers/issues" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +"lightspeed_tool_runtime" = ["*.yaml", "*.yml"] + +[tool.setuptools.data-files] +config = ["config/*.yaml"] \ No newline at end of file diff --git a/providers/tool-runtime/lightspeed-tool-runtime/src/__init__.py b/providers/tool-runtime/lightspeed-tool-runtime/src/__init__.py new file mode 100644 index 0000000..3877b00 --- /dev/null +++ b/providers/tool-runtime/lightspeed-tool-runtime/src/__init__.py @@ -0,0 +1 @@ +# Package initialization \ No newline at end of file diff --git a/lightspeed_stack_providers/providers/remote/tool_runtime/lightspeed/__init__.py b/providers/tool-runtime/lightspeed-tool-runtime/src/lightspeed_tool_runtime/__init__.py similarity index 100% rename from lightspeed_stack_providers/providers/remote/tool_runtime/lightspeed/__init__.py rename to providers/tool-runtime/lightspeed-tool-runtime/src/lightspeed_tool_runtime/__init__.py diff --git a/lightspeed_stack_providers/providers/remote/tool_runtime/lightspeed/config.py b/providers/tool-runtime/lightspeed-tool-runtime/src/lightspeed_tool_runtime/config.py similarity index 100% rename from lightspeed_stack_providers/providers/remote/tool_runtime/lightspeed/config.py rename to providers/tool-runtime/lightspeed-tool-runtime/src/lightspeed_tool_runtime/config.py diff --git a/lightspeed_stack_providers/providers/remote/tool_runtime/lightspeed/lightspeed.py b/providers/tool-runtime/lightspeed-tool-runtime/src/lightspeed_tool_runtime/lightspeed.py similarity index 100% rename from lightspeed_stack_providers/providers/remote/tool_runtime/lightspeed/lightspeed.py rename to providers/tool-runtime/lightspeed-tool-runtime/src/lightspeed_tool_runtime/lightspeed.py diff --git a/providers/vector-dbs/.gitkeep b/providers/vector-dbs/.gitkeep new file mode 100644 index 0000000..8438325 --- /dev/null +++ b/providers/vector-dbs/.gitkeep @@ -0,0 +1,2 @@ +# This file ensures the vector-dbs directory is tracked by Git +# Future vector-dbs providers will be added here \ No newline at end of file diff --git a/providers/vector-io/.gitkeep b/providers/vector-io/.gitkeep new file mode 100644 index 0000000..66b6b67 --- /dev/null +++ b/providers/vector-io/.gitkeep @@ -0,0 +1,2 @@ +# This file ensures the vector-io directory is tracked by Git +# Future vector-io providers will be added here \ No newline at end of file diff --git a/scripts/build_reorganized.py b/scripts/build_reorganized.py new file mode 100755 index 0000000..cc38fa5 --- /dev/null +++ b/scripts/build_reorganized.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +""" +Build script for the reorganized provider packages. + +This script builds all provider packages from the new organized structure. +""" + +import os +import subprocess +import sys +from pathlib import Path +from typing import Dict, List, Optional + +# Provider definitions with their new locations +PROVIDERS = { + "lightspeed-inline-agent": { + "path": "providers/agents/lightspeed-inline-agent", + "description": "Lightspeed inline agent provider for llama-stack", + "dependencies": ["llama-stack==0.2.16", "httpx", "pydantic>=2.10.6"], + }, + "lightspeed-agent": { + "path": "providers/agents/lightspeed-agent", + "description": "Lightspeed remote agent provider for llama-stack", + "dependencies": ["llama-stack==0.2.16", "httpx", "pydantic>=2.10.6"], + }, + "lightspeed-question-validity": { + "path": "providers/safety/lightspeed-question-validity", + "description": "Lightspeed question validity safety provider for llama-stack", + "dependencies": ["llama-stack==0.2.16", "httpx", "pydantic>=2.10.6"], + }, + "lightspeed-redaction": { + "path": "providers/safety/lightspeed-redaction", + "description": "Lightspeed redaction safety provider for llama-stack", + "dependencies": ["llama-stack==0.2.16", "httpx", "pydantic>=2.10.6"], + }, + "lightspeed-tool-runtime": { + "path": "providers/tool-runtime/lightspeed-tool-runtime", + "description": "Lightspeed tool runtime provider for llama-stack", + "dependencies": ["llama-stack==0.2.16", "httpx", "pydantic>=2.10.6", "mcp"], + }, +} + + +def get_version() -> str: + """Get version from the main pyproject.toml""" + import tomllib + + with open("pyproject.toml", "rb") as f: + data = tomllib.load(f) + return data["project"]["version"] + + +def build_package(provider_name: str, provider_config: Dict, version: str) -> bool: + """Build a single package""" + package_path = Path(provider_config["path"]) + + if not package_path.exists(): + print(f"Error: Package path does not exist: {package_path}") + return False + + try: + # Change to package directory + original_dir = os.getcwd() + os.chdir(package_path) + + # Update version in pyproject.toml + update_version_in_pyproject(version) + + # Build the package + result = subprocess.run( + [sys.executable, "-m", "build"], + capture_output=True, + text=True + ) + + if result.returncode != 0: + print(f"Error building {provider_name}: {result.stderr}") + return False + + print(f"Successfully built {provider_name}") + return True + + except Exception as e: + print(f"Error building {provider_name}: {e}") + return False + finally: + # Return to original directory + os.chdir(original_dir) + + +def update_version_in_pyproject(version: str) -> None: + """Update version in pyproject.toml""" + pyproject_path = Path("pyproject.toml") + + if not pyproject_path.exists(): + return + + # Read current content + with open(pyproject_path, "r") as f: + content = f.read() + + # Update version + import re + content = re.sub(r'version = ".*?"', f'version = "{version}"', content) + + # Write back + with open(pyproject_path, "w") as f: + f.write(content) + + +def main(): + """Main build function""" + import argparse + + parser = argparse.ArgumentParser(description="Build reorganized provider packages") + parser.add_argument("--provider", help="Build specific provider only") + parser.add_argument("--version", help="Override version (defaults to main pyproject.toml)") + + args = parser.parse_args() + + # Get version + version = args.version or get_version() + + # Determine which providers to build + providers_to_build = [args.provider] if args.provider else PROVIDERS.keys() + + if args.provider and args.provider not in PROVIDERS: + print(f"Unknown provider: {args.provider}") + print(f"Available providers: {list(PROVIDERS.keys())}") + sys.exit(1) + + success_count = 0 + total_count = len(providers_to_build) + + for provider_name in providers_to_build: + if provider_name not in PROVIDERS: + print(f"Skipping unknown provider: {provider_name}") + continue + + print(f"\nBuilding {provider_name}...") + + provider_config = PROVIDERS[provider_name] + + # Build the package + if build_package(provider_name, provider_config, version): + success_count += 1 + + print(f"\nBuild complete: {success_count}/{total_count} packages built successfully") + + if success_count < total_count: + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/generate_provider_template.py b/scripts/generate_provider_template.py new file mode 100644 index 0000000..9d95bf0 --- /dev/null +++ b/scripts/generate_provider_template.py @@ -0,0 +1,1152 @@ +#!/usr/bin/env python3 +""" +Template generator for llama-stack external providers. + +This script generates a complete template for a new external provider +that follows the llama-stack provider format and can be published to PyPI. +""" + +import os +import sys +from pathlib import Path +from typing import Dict, List, Optional + +# Provider type templates +PROVIDER_TEMPLATES = { + "inline-agent": { + "description": "Inline agent provider for llama-stack", + "yaml_template": """api: Api.agents +provider_type: inline::{provider_name} +config_class: {package_name}.config.{ConfigClass} +module: {package_name} +adapter: + adapter_type: {adapter_type} + pip_packages: [] + config_class: {package_name}.config.{ConfigClass} + module: {package_name} +api_dependencies: [ inference, safety, vector_io, vector_dbs, tool_runtime, tool_groups ] +optional_api_dependencies: [] +""", + "inline-inference": { + "description": "Inline inference provider for llama-stack", + "yaml_template": """api: Api.inference +provider_type: inline::{provider_name} +config_class: {package_name}.config.{ConfigClass} +module: {package_name} +adapter: + adapter_type: {adapter_type} + pip_packages: [] + config_class: {package_name}.config.{ConfigClass} + module: {package_name} +api_dependencies: [] +optional_api_dependencies: [] +""", + "init_template": """from typing import Any + +from llama_stack.distribution.datatypes import AccessRule, Api + +from .config import {ConfigClass} + + +async def get_provider_impl( + config: {ConfigClass}, deps: dict[Api, Any], policy: list[AccessRule] +): + from .inference import {ImplClass} + + impl = {ImplClass}(config, deps, policy) + await impl.initialize() + return impl +""", + "config_template": """from typing import Any, Optional +from pydantic import BaseModel + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} inference provider\"\"\" + + # Add your configuration fields here + model_id: str = "gpt-3.5-turbo" + + @classmethod + def sample_run_config(cls, __distro_dir__: str) -> dict[str, Any]: + return {{ + "model_id": "${{env.MODEL_ID:gpt-3.5-turbo}}", + }} +""", + "inference_template": """from typing import Any +from llama_stack.log import get_logger + +logger = get_logger(name=__name__, category="inference") + + +class {ImplClass}: + def __init__(self, config, deps, policy): + self.config = config + self.deps = deps + self.policy = policy + + async def initialize(self): + \"\"\"Initialize the inference provider\"\"\" + # Add your initialization logic here + pass + + async def chat_completion(self, model_id: str, messages: list, **kwargs: Any): + \"\"\"Handle chat completion requests\"\"\" + # Add your chat completion logic here + pass +""", + }, + "inline-vector-io": { + "description": "Inline vector I/O provider for llama-stack", + "yaml_template": """api: Api.vector_io +provider_type: inline::{provider_name} +config_class: {package_name}.config.{ConfigClass} +module: {package_name} +adapter: + adapter_type: {adapter_type} + pip_packages: [] + config_class: {package_name}.config.{ConfigClass} + module: {package_name} +api_dependencies: [] +optional_api_dependencies: [] +""", + "init_template": """from typing import Any + +from llama_stack.distribution.datatypes import AccessRule, Api + +from .config import {ConfigClass} + + +async def get_provider_impl( + config: {ConfigClass}, deps: dict[Api, Any], policy: list[AccessRule] +): + from .vector_io import {ImplClass} + + impl = {ImplClass}(config, deps, policy) + await impl.initialize() + return impl +""", + "config_template": """from typing import Any, Optional +from pydantic import BaseModel + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} vector I/O provider\"\"\" + + # Add your configuration fields here + enabled: bool = True + + @classmethod + def sample_run_config(cls, __distro_dir__: str) -> dict[str, Any]: + return {{ + "enabled": True, + }} +""", + "vector_io_template": """from typing import Any +from llama_stack.log import get_logger + +logger = get_logger(name=__name__, category="vector_io") + + +class {ImplClass}: + def __init__(self, config, deps, policy): + self.config = config + self.deps = deps + self.policy = policy + + async def initialize(self): + \"\"\"Initialize the vector I/O provider\"\"\" + # Add your initialization logic here + pass + + async def read_vectors(self, collection: str, **kwargs: Any): + \"\"\"Read vectors from storage\"\"\" + # Add your vector reading logic here + pass + + async def write_vectors(self, collection: str, vectors: list, **kwargs: Any): + \"\"\"Write vectors to storage\"\"\" + # Add your vector writing logic here + pass +""", + }, + "inline-vector-dbs": { + "description": "Inline vector database provider for llama-stack", + "yaml_template": """api: Api.vector_dbs +provider_type: inline::{provider_name} +config_class: {package_name}.config.{ConfigClass} +module: {package_name} +adapter: + adapter_type: {adapter_type} + pip_packages: [] + config_class: {package_name}.config.{ConfigClass} + module: {package_name} +api_dependencies: [] +optional_api_dependencies: [] +""", + "init_template": """from typing import Any + +from llama_stack.distribution.datatypes import AccessRule, Api + +from .config import {ConfigClass} + + +async def get_provider_impl( + config: {ConfigClass}, deps: dict[Api, Any], policy: list[AccessRule] +): + from .vector_dbs import {ImplClass} + + impl = {ImplClass}(config, deps, policy) + await impl.initialize() + return impl +""", + "config_template": """from typing import Any, Optional +from pydantic import BaseModel + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} vector database provider\"\"\" + + # Add your configuration fields here + enabled: bool = True + + @classmethod + def sample_run_config(cls, __distro_dir__: str) -> dict[str, Any]: + return {{ + "enabled": True, + }} +""", + "vector_dbs_template": """from typing import Any +from llama_stack.log import get_logger + +logger = get_logger(name=__name__, category="vector_dbs") + + +class {ImplClass}: + def __init__(self, config, deps, policy): + self.config = config + self.deps = deps + self.policy = policy + + async def initialize(self): + \"\"\"Initialize the vector database provider\"\"\" + # Add your initialization logic here + pass + + async def create_collection(self, name: str, **kwargs: Any): + \"\"\"Create a new collection\"\"\" + # Add your collection creation logic here + pass + + async def delete_collection(self, name: str): + \"\"\"Delete a collection\"\"\" + # Add your collection deletion logic here + pass +""", + }, + "inline-tool-groups": { + "description": "Inline tool groups provider for llama-stack", + "yaml_template": """api: Api.tool_groups +provider_type: inline::{provider_name} +config_class: {package_name}.config.{ConfigClass} +module: {package_name} +adapter: + adapter_type: {adapter_type} + pip_packages: [] + config_class: {package_name}.config.{ConfigClass} + module: {package_name} +api_dependencies: [] +optional_api_dependencies: [] +""", + "init_template": """from typing import Any + +from llama_stack.distribution.datatypes import AccessRule, Api + +from .config import {ConfigClass} + + +async def get_provider_impl( + config: {ConfigClass}, deps: dict[Api, Any], policy: list[AccessRule] +): + from .tool_groups import {ImplClass} + + impl = {ImplClass}(config, deps, policy) + await impl.initialize() + return impl +""", + "config_template": """from typing import Any, Optional +from pydantic import BaseModel + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} tool groups provider\"\"\" + + # Add your configuration fields here + enabled: bool = True + + @classmethod + def sample_run_config(cls, __distro_dir__: str) -> dict[str, Any]: + return {{ + "enabled": True, + }} +""", + "tool_groups_template": """from typing import Any +from llama_stack.log import get_logger + +logger = get_logger(name=__name__, category="tool_groups") + + +class {ImplClass}: + def __init__(self, config, deps, policy): + self.config = config + self.deps = deps + self.policy = policy + + async def initialize(self): + \"\"\"Initialize the tool groups provider\"\"\" + # Add your initialization logic here + pass + + async def get_tool_groups(self) -> list[Any]: + \"\"\"Get available tool groups\"\"\" + # Add your tool groups logic here + return [] +""", + }, + "init_template": """from typing import Any + +from llama_stack.distribution.datatypes import AccessRule, Api + +from .config import {ConfigClass} + + +async def get_provider_impl( + config: {ConfigClass}, deps: dict[Api, Any], policy: list[AccessRule] +): + from .agents import {ImplClass} + + impl = {ImplClass}( + config, + deps[Api.inference], + deps[Api.vector_io], + deps[Api.safety], + deps[Api.tool_runtime], + deps[Api.tool_groups], + policy, + ) + await impl.initialize() + return impl +""", + "config_template": """from typing import Any, Optional +from pydantic import BaseModel + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} provider\"\"\" + + # Add your configuration fields here + example_field: Optional[str] = None + + @classmethod + def sample_run_config(cls, __distro_dir__: str) -> dict[str, Any]: + return {{ + "example_field": "${{env.EXAMPLE_FIELD:}}", + }} +""", + "agents_template": """import json +import uuid +from collections.abc import AsyncGenerator + +from llama_stack.apis.agents import AgentConfig, AgentTurnCreateRequest, StepType +from llama_stack.distribution.datatypes import AccessRule +from llama_stack.log import get_logger +from llama_stack.providers.inline.agents.meta_reference.agent_instance import ChatAgent +from llama_stack.providers.utils.telemetry import tracing + +from llama_stack.apis.inference import ( + Inference, + UserMessage, + SamplingParams, + TopPSamplingStrategy, +) +from llama_stack.apis.safety import Safety +from llama_stack.apis.tools import ToolGroups, ToolRuntime +from llama_stack.apis.vector_io import VectorIO +from llama_stack.providers.utils.kvstore import KVStore + +logger = get_logger(name=__name__, category="agents") + + +class {ImplClass}(ChatAgent): + def __init__( + self, + agent_config: AgentConfig, + inference_api: Inference, + vector_io_api: VectorIO, + safety_api: Safety, + tool_runtime_api: ToolRuntime, + tool_groups_api: ToolGroups, + policy: list[AccessRule], + ): + super().__init__( + agent_config, + inference_api, + vector_io_api, + safety_api, + tool_runtime_api, + tool_groups_api, + policy, + ) + + async def initialize(self): + \"\"\"Initialize the agent\"\"\" + # Add your initialization logic here + pass + + async def chat( + self, request: AgentTurnCreateRequest + ) -> AsyncGenerator[StepType, None]: + \"\"\"Handle chat requests\"\"\" + # Add your chat implementation here + yield StepType.thinking + # Your implementation here + yield StepType.complete +""", + }, + + "remote-agent": { + "description": "Remote agent provider for llama-stack", + "yaml_template": """api: Api.agents +adapter: + adapter_type: {adapter_type} + pip_packages: [] + config_class: {package_name}.config.{ConfigClass} + module: {package_name} +api_dependencies: [] +optional_api_dependencies: [] +""", + "init_template": """from .{module_name} import {ImplClass} +from .config import {ConfigClass} + + +async def get_adapter_impl(config: {ConfigClass}, _deps): + impl = {ImplClass}(config) + await impl.initialize() + return impl +""", + "config_template": """from typing import Any +from pydantic import BaseModel, Field + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} provider\"\"\" + + # Add your configuration fields here + api_key: str | None = Field( + default=None, + description="The API Key", + ) + api_url: str | None = Field( + default=None, + description="The API URL", + ) + + @classmethod + def sample_run_config(cls, __distro_dir__: str) -> dict[str, Any]: + return {{ + "api_key": "${{env.API_KEY:}}", + "api_url": "${{env.API_URL:http://localhost:8080}}", + }} +""", + "module_template": """from typing import Any +from llama_stack.apis.agents import AgentConfig, AgentTurnCreateRequest, StepType +from llama_stack.log import get_logger + +logger = get_logger(name=__name__, category="agents") + + +class {ImplClass}: + def __init__(self, config): + self.config = config + + async def initialize(self): + \"\"\"Initialize the remote agent\"\"\" + # Add your initialization logic here + pass + + async def chat( + self, request: AgentTurnCreateRequest + ) -> AsyncGenerator[StepType, None]: + \"\"\"Handle chat requests\"\"\" + # Add your chat implementation here + yield StepType.thinking + # Your implementation here + yield StepType.complete +""", + }, + + "remote-tool-runtime": { + "description": "Remote tool runtime provider for llama-stack", + "yaml_template": """api: Api.tool_runtime +adapter: + adapter_type: {adapter_type} + pip_packages: [] + config_class: {package_name}.config.{ConfigClass} + module: {package_name} + provider_data_validator: {package_name}.{ImplClass}ProviderDataValidator +api_dependencies: [] +optional_api_dependencies: [] +""", + "remote-inference": { + "description": "Remote inference provider for llama-stack", + "yaml_template": """api: Api.inference +adapter: + adapter_type: {adapter_type} + pip_packages: [] + config_class: {package_name}.config.{ConfigClass} + module: {package_name} +api_dependencies: [] +optional_api_dependencies: [] +""", + "init_template": """from .{module_name} import {ImplClass} +from .config import {ConfigClass} + + +async def get_adapter_impl(config: {ConfigClass}, _deps): + impl = {ImplClass}(config) + await impl.initialize() + return impl +""", + "config_template": """from typing import Any +from pydantic import BaseModel, Field + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} provider\"\"\" + + # Add your configuration fields here + api_key: str | None = Field( + default=None, + description="The API Key", + ) + api_url: str | None = Field( + default=None, + description="The API URL", + ) + + @classmethod + def sample_run_config(cls, __distro_dir__: str) -> dict[str, Any]: + return {{ + "api_key": "${{env.API_KEY:}}", + "api_url": "${{env.API_URL:http://localhost:8080}}", + }} +""", + "module_template": """from typing import Any +from llama_stack.log import get_logger + +logger = get_logger(name=__name__, category="inference") + + +class {ImplClass}: + def __init__(self, config): + self.config = config + + async def initialize(self): + \"\"\"Initialize the inference provider\"\"\" + # Add your initialization logic here + pass + + async def chat_completion(self, model_id: str, messages: list, **kwargs: Any): + \"\"\"Handle chat completion requests\"\"\" + # Add your chat completion logic here + pass +""", + }, + "remote-vector-io": { + "description": "Remote vector I/O provider for llama-stack", + "yaml_template": """api: Api.vector_io +adapter: + adapter_type: {adapter_type} + pip_packages: [] + config_class: {package_name}.config.{ConfigClass} + module: {package_name} +api_dependencies: [] +optional_api_dependencies: [] +""", + "init_template": """from .{module_name} import {ImplClass} +from .config import {ConfigClass} + + +async def get_adapter_impl(config: {ConfigClass}, _deps): + impl = {ImplClass}(config) + await impl.initialize() + return impl +""", + "config_template": """from typing import Any +from pydantic import BaseModel, Field + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} provider\"\"\" + + # Add your configuration fields here + api_key: str | None = Field( + default=None, + description="The API Key", + ) + api_url: str | None = Field( + default=None, + description="The API URL", + ) + + @classmethod + def sample_run_config(cls, __distro_dir__: str) -> dict[str, Any]: + return {{ + "api_key": "${{env.API_KEY:}}", + "api_url": "${{env.API_URL:http://localhost:8080}}", + }} +""", + "module_template": """from typing import Any +from llama_stack.log import get_logger + +logger = get_logger(name=__name__, category="vector_io") + + +class {ImplClass}: + def __init__(self, config): + self.config = config + + async def initialize(self): + \"\"\"Initialize the vector I/O provider\"\"\" + # Add your initialization logic here + pass + + async def read_vectors(self, collection: str, **kwargs: Any): + \"\"\"Read vectors from storage\"\"\" + # Add your vector reading logic here + pass + + async def write_vectors(self, collection: str, vectors: list, **kwargs: Any): + \"\"\"Write vectors to storage\"\"\" + # Add your vector writing logic here + pass +""", + }, + "remote-vector-dbs": { + "description": "Remote vector database provider for llama-stack", + "yaml_template": """api: Api.vector_dbs +adapter: + adapter_type: {adapter_type} + pip_packages: [] + config_class: {package_name}.config.{ConfigClass} + module: {package_name} +api_dependencies: [] +optional_api_dependencies: [] +""", + "init_template": """from .{module_name} import {ImplClass} +from .config import {ConfigClass} + + +async def get_adapter_impl(config: {ConfigClass}, _deps): + impl = {ImplClass}(config) + await impl.initialize() + return impl +""", + "config_template": """from typing import Any +from pydantic import BaseModel, Field + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} provider\"\"\" + + # Add your configuration fields here + api_key: str | None = Field( + default=None, + description="The API Key", + ) + api_url: str | None = Field( + default=None, + description="The API URL", + ) + + @classmethod + def sample_run_config(cls, __distro_dir__: str) -> dict[str, Any]: + return {{ + "api_key": "${{env.API_KEY:}}", + "api_url": "${{env.API_URL:http://localhost:8080}}", + }} +""", + "module_template": """from typing import Any +from llama_stack.log import get_logger + +logger = get_logger(name=__name__, category="vector_dbs") + + +class {ImplClass}: + def __init__(self, config): + self.config = config + + async def initialize(self): + \"\"\"Initialize the vector database provider\"\"\" + # Add your initialization logic here + pass + + async def create_collection(self, name: str, **kwargs: Any): + \"\"\"Create a new collection\"\"\" + # Add your collection creation logic here + pass + + async def delete_collection(self, name: str): + \"\"\"Delete a collection\"\"\" + # Add your collection deletion logic here + pass +""", + }, + "remote-tool-groups": { + "description": "Remote tool groups provider for llama-stack", + "yaml_template": """api: Api.tool_groups +adapter: + adapter_type: {adapter_type} + pip_packages: [] + config_class: {package_name}.config.{ConfigClass} + module: {package_name} +api_dependencies: [] +optional_api_dependencies: [] +""", + "init_template": """from .{module_name} import {ImplClass} +from .config import {ConfigClass} + + +async def get_adapter_impl(config: {ConfigClass}, _deps): + impl = {ImplClass}(config) + await impl.initialize() + return impl +""", + "config_template": """from typing import Any +from pydantic import BaseModel, Field + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} provider\"\"\" + + # Add your configuration fields here + api_key: str | None = Field( + default=None, + description="The API Key", + ) + api_url: str | None = Field( + default=None, + description="The API URL", + ) + + @classmethod + def sample_run_config(cls, __distro_dir__: str) -> dict[str, Any]: + return {{ + "api_key": "${{env.API_KEY:}}", + "api_url": "${{env.API_URL:http://localhost:8080}}", + }} +""", + "module_template": """from typing import Any +from llama_stack.log import get_logger + +logger = get_logger(name=__name__, category="tool_groups") + + +class {ImplClass}: + def __init__(self, config): + self.config = config + + async def initialize(self): + \"\"\"Initialize the tool groups provider\"\"\" + # Add your initialization logic here + pass + + async def get_tool_groups(self) -> list[Any]: + \"\"\"Get available tool groups\"\"\" + # Add your tool groups logic here + return [] +""", + }, + "init_template": """from typing import Any, Optional + +from pydantic import BaseModel + +from llama_stack.distribution.datatypes import Api + +from .config import {ConfigClass} + + +class {ImplClass}ProviderDataValidator(BaseModel): + \"\"\"Data validator for {provider_name} provider\"\"\" + # Add your validation fields here + headers: Optional[dict[str, dict[str, str]]] = {{"*": {{}}}} + + +async def get_adapter_impl(config: {ConfigClass}, _deps: dict[Api, Any]): + from .{module_name} import {ImplClass} + + impl = {ImplClass}(config) + + await impl.initialize() + return impl +""", + "config_template": """from typing import Any + +from pydantic import BaseModel + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} tool runtime\"\"\" + + # Add your configuration fields here + api_key: str | None = None + + @classmethod + def sample_run_config(cls, __distro_dir__: str, **kwargs: Any) -> dict[str, Any]: + return {{ + "api_key": "${{env.API_KEY:}}", + }} +""", + "module_template": """from typing import Any +from llama_stack.log import get_logger + +logger = get_logger(name=__name__, category="tool_runtime") + + +class {ImplClass}: + def __init__(self, config): + self.config = config + + async def initialize(self): + \"\"\"Initialize the tool runtime\"\"\" + # Add your initialization logic here + pass + + async def execute_tool(self, tool_name: str, **kwargs: Any) -> Any: + \"\"\"Execute a tool\"\"\" + # Add your tool execution logic here + pass +""", + }, + + "inline-safety": { + "description": "Inline safety provider for llama-stack", + "yaml_template": """module: {package_name} +config_class: {package_name}.config.{ConfigClass} +pip_packages: [] +api_dependencies: + - inference +optional_api_dependencies: [] +""", + "init_template": """from typing import Any + +from .config import {ConfigClass} + + +async def get_provider_impl( + config: {ConfigClass}, + deps: dict[str, Any], +): + from .safety import {ImplClass} + + assert isinstance( + config, {ConfigClass} + ), f"Unexpected config type: {{type(config)}}" + + impl = {ImplClass}(config, deps) + await impl.initialize() + return impl +""", + "config_template": """from typing import Any, Optional +from pydantic import BaseModel + + +class {ConfigClass}(BaseModel): + \"\"\"Configuration for {provider_name} safety provider\"\"\" + + # Add your configuration fields here + enabled: bool = True + + @classmethod + def sample_run_config(cls, __distro_dir__: str) -> dict[str, Any]: + return {{ + "enabled": True, + }} +""", + "safety_template": """from typing import Any +from llama_stack.log import get_logger + +logger = get_logger(name=__name__, category="safety") + + +class {ImplClass}: + def __init__(self, config, deps): + self.config = config + self.deps = deps + + async def initialize(self): + \"\"\"Initialize the safety provider\"\"\" + # Add your initialization logic here + pass + + async def validate(self, content: str) -> bool: + \"\"\"Validate content for safety\"\"\" + # Add your validation logic here + return True +""", + }, +} + + +def generate_provider_template( + provider_name: str, + provider_type: str, + output_dir: str = ".", + description: Optional[str] = None, +) -> None: + """Generate a complete provider template""" + + if provider_type not in PROVIDER_TEMPLATES: + print(f"Error: Unknown provider type '{provider_type}'") + print(f"Available types: {list(PROVIDER_TEMPLATES.keys())}") + sys.exit(1) + + # Convert provider name to package name + package_name = provider_name.replace("-", "_") + + # Generate class names + ConfigClass = "".join(word.capitalize() for word in provider_name.split("-")) + "Config" + ImplClass = "".join(word.capitalize() for word in provider_name.split("-")) + "Impl" + adapter_type = provider_name.replace("-", "_") + module_name = provider_name.replace("-", "_") + + # Get template + template = PROVIDER_TEMPLATES[provider_type] + + # Create output directory + output_path = Path(output_dir) / provider_name + output_path.mkdir(parents=True, exist_ok=True) + + # Generate files + files_to_create = [ + ("pyproject.toml", generate_pyproject_toml(provider_name, template["description"])), + ("README.md", generate_readme(provider_name, template["description"])), + ("src/__init__.py", ""), + (f"src/{package_name}/__init__.py", template["init_template"].format( + package_name=package_name, + ConfigClass=ConfigClass, + ImplClass=ImplClass, + adapter_type=adapter_type, + module_name=module_name, + )), + (f"src/{package_name}/config.py", template["config_template"].format( + ConfigClass=ConfigClass, + provider_name=provider_name, + )), + ] + + # Add specific module files based on provider type + if provider_type == "inline-agent": + files_to_create.append((f"src/{package_name}/agents.py", template["agents_template"].format( + ImplClass=ImplClass, + ))) + elif provider_type == "remote-agent": + files_to_create.append((f"src/{package_name}/{module_name}.py", template["module_template"].format( + ImplClass=ImplClass, + ))) + elif provider_type == "remote-tool-runtime": + files_to_create.append((f"src/{package_name}/{module_name}.py", template["module_template"].format( + ImplClass=ImplClass, + ))) + elif provider_type == "inline-safety": + files_to_create.append((f"src/{package_name}/safety.py", template["safety_template"].format( + ImplClass=ImplClass, + ))) + elif provider_type == "inline-inference": + files_to_create.append((f"src/{package_name}/inference.py", template["inference_template"].format( + ImplClass=ImplClass, + ))) + elif provider_type == "inline-vector-io": + files_to_create.append((f"src/{package_name}/vector_io.py", template["vector_io_template"].format( + ImplClass=ImplClass, + ))) + elif provider_type == "inline-vector-dbs": + files_to_create.append((f"src/{package_name}/vector_dbs.py", template["vector_dbs_template"].format( + ImplClass=ImplClass, + ))) + elif provider_type == "inline-tool-groups": + files_to_create.append((f"src/{package_name}/tool_groups.py", template["tool_groups_template"].format( + ImplClass=ImplClass, + ))) + elif provider_type in ["remote-inference", "remote-vector-io", "remote-vector-dbs", "remote-tool-groups"]: + files_to_create.append((f"src/{package_name}/{module_name}.py", template["module_template"].format( + ImplClass=ImplClass, + ))) + + # Create files + for file_path, content in files_to_create: + full_path = output_path / file_path + full_path.parent.mkdir(parents=True, exist_ok=True) + + with open(full_path, "w") as f: + f.write(content) + + # Generate YAML config + yaml_content = template["yaml_template"].format( + package_name=package_name, + ConfigClass=ConfigClass, + adapter_type=adapter_type, + provider_name=provider_name, + ImplClass=ImplClass, + ) + + config_path = output_path / "config" / f"{provider_name}.yaml" + config_path.parent.mkdir(exist_ok=True) + + with open(config_path, "w") as f: + f.write(yaml_content) + + print(f"✅ Generated provider template: {output_path}") + print(f"📁 Package structure created") + print(f"📄 YAML config: {config_path}") + print(f"📦 Ready for PyPI publishing") + + +def generate_pyproject_toml(provider_name: str, description: str) -> str: + """Generate pyproject.toml content""" + return f"""[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "{provider_name}" +version = "0.1.0" +description = "{description}" +readme = "README.md" +license = {{text = "MIT"}} +requires-python = ">=3.12" +authors = [ + {{name = "Your Name", email = "your.email@example.com"}} +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "llama-stack==0.2.16", + "httpx", + "pydantic>=2.10.6", +] + +[project.urls] +Homepage = "https://github.com/your-org/{provider_name}" +Repository = "https://github.com/your-org/{provider_name}" +Issues = "https://github.com/your-org/{provider_name}/issues" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +"{provider_name.replace('-', '_')}" = ["*.yaml", "*.yml"] + +[tool.setuptools.data-files] +config = ["config/*.yaml"] +""" + + +def generate_readme(provider_name: str, description: str) -> str: + """Generate README.md content""" + return f"""# {provider_name} + +{description} + +## Installation + +```bash +pip install {provider_name} +``` + +## Usage + +This provider is designed to work with llama-stack. + +### Configuration + +Add the provider configuration to your llama-stack configuration: + +```yaml +# Example configuration +providers: + {provider_name}: + enabled: true + # Add your configuration here +``` + +### Integration + +The provider will be automatically loaded by llama-stack when properly configured. + +## Development + +### Local Development + +```bash +# Clone the repository +git clone +cd {provider_name} + +# Install in development mode +pip install -e . + +# Run tests +python -m pytest +``` + +### Building + +```bash +# Build the package +python -m build + +# Install from wheel +pip install dist/*.whl +``` + +## License + +MIT License - see LICENSE file for details. +""" + + +def main(): + """Main function""" + import argparse + + parser = argparse.ArgumentParser(description="Generate llama-stack provider template") + parser.add_argument("provider_name", help="Name of the provider (kebab-case)") + parser.add_argument("provider_type", help="Type of provider", + choices=list(PROVIDER_TEMPLATES.keys())) + parser.add_argument("--output-dir", default=".", help="Output directory") + parser.add_argument("--description", help="Custom description") + + args = parser.parse_args() + + # Validate provider name + if not args.provider_name.replace("-", "").isalnum(): + print("Error: Provider name must contain only letters, numbers, and hyphens") + sys.exit(1) + + # Generate template + generate_provider_template( + args.provider_name, + args.provider_type, + args.output_dir, + args.description, + ) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/test_build.py b/scripts/test_build.py new file mode 100644 index 0000000..e3ca3e8 --- /dev/null +++ b/scripts/test_build.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Test script for validating the build process and package structure. +""" + +import os +import subprocess +import sys +import tempfile +import zipfile +from pathlib import Path +from typing import Dict, List, Optional + +def test_build_script(): + """Test the build script functionality""" + print("Testing build script...") + + # Test building a single package + result = subprocess.run([ + sys.executable, "scripts/build_packages.py", + "--provider", "lightspeed-inline-agent", + "--build-dir", "test_build" + ], capture_output=True, text=True) + + if result.returncode != 0: + print(f"Build script failed: {result.stderr}") + return False + + print("✓ Build script executed successfully") + return True + +def test_package_structure(package_name: str, build_dir: Path): + """Test the structure of a built package""" + print(f"Testing package structure for {package_name}...") + + package_dir = build_dir / package_name + + # Check required files exist + required_files = [ + "pyproject.toml", + "README.md", + "src", + "config" + ] + + for file_path in required_files: + if not (package_dir / file_path).exists(): + print(f"✗ Missing required file/directory: {file_path}") + return False + + # Check source structure + src_dir = package_dir / "src" / package_name.replace("-", "_") + if not src_dir.exists(): + print(f"✗ Missing source directory: {src_dir}") + return False + + # Check for Python files + py_files = list(src_dir.rglob("*.py")) + if not py_files: + print(f"✗ No Python files found in {src_dir}") + return False + + print(f"✓ Package structure valid for {package_name}") + return True + +def test_pyproject_toml(package_name: str, build_dir: Path): + """Test the pyproject.toml file""" + print(f"Testing pyproject.toml for {package_name}...") + + pyproject_path = build_dir / package_name / "pyproject.toml" + + if not pyproject_path.exists(): + print(f"✗ pyproject.toml not found: {pyproject_path}") + return False + + # Read and validate pyproject.toml + try: + import tomllib + with open(pyproject_path, "rb") as f: + data = tomllib.load(f) + + # Check required fields + project = data.get("project", {}) + required_fields = ["name", "version", "description", "dependencies"] + + for field in required_fields: + if field not in project: + print(f"✗ Missing required field in pyproject.toml: {field}") + return False + + # Validate package name + if project["name"] != package_name: + print(f"✗ Package name mismatch: expected {package_name}, got {project['name']}") + return False + + print(f"✓ pyproject.toml valid for {package_name}") + return True + + except Exception as e: + print(f"✗ Error parsing pyproject.toml: {e}") + return False + +def test_package_build(package_name: str, build_dir: Path): + """Test building the package""" + print(f"Testing package build for {package_name}...") + + package_dir = build_dir / package_name + + try: + # Change to package directory + original_dir = os.getcwd() + os.chdir(package_dir) + + # Build the package + result = subprocess.run([ + sys.executable, "-m", "build" + ], capture_output=True, text=True) + + if result.returncode != 0: + print(f"✗ Package build failed: {result.stderr}") + return False + + # Check for built artifacts + dist_dir = package_dir / "dist" + if not dist_dir.exists(): + print(f"✗ No dist directory created") + return False + + artifacts = list(dist_dir.glob("*")) + if not artifacts: + print(f"✗ No build artifacts found") + return False + + print(f"✓ Package build successful for {package_name}") + return True + + except Exception as e: + print(f"✗ Error building package: {e}") + return False + finally: + # Return to original directory + os.chdir(original_dir) + +def test_package_install(package_name: str, build_dir: Path): + """Test installing the package""" + print(f"Testing package installation for {package_name}...") + + package_dir = build_dir / package_name + dist_dir = package_dir / "dist" + + # Find wheel file + wheel_files = list(dist_dir.glob("*.whl")) + if not wheel_files: + print(f"✗ No wheel file found for {package_name}") + return False + + wheel_file = wheel_files[0] + + try: + # Install the package + result = subprocess.run([ + sys.executable, "-m", "pip", "install", str(wheel_file), "--force-reinstall" + ], capture_output=True, text=True) + + if result.returncode != 0: + print(f"✗ Package installation failed: {result.stderr}") + return False + + # Test import + module_name = package_name.replace("-", "_") + try: + __import__(module_name) + print(f"✓ Package installation and import successful for {package_name}") + return True + except ImportError as e: + print(f"✗ Package import failed: {e}") + return False + + except Exception as e: + print(f"✗ Error testing package installation: {e}") + return False + +def test_all_packages(): + """Test all packages""" + print("Testing all packages...") + + packages = [ + "lightspeed-inline-agent", + "lightspeed-agent", + "lightspeed-tool-runtime", + "lightspeed-question-validity", + "lightspeed-redaction" + ] + + build_dir = Path("test_build") + success_count = 0 + total_count = len(packages) + + for package_name in packages: + print(f"\n--- Testing {package_name} ---") + + package_success = True + + # Test package structure + if not test_package_structure(package_name, build_dir): + package_success = False + + # Test pyproject.toml + if not test_pyproject_toml(package_name, build_dir): + package_success = False + + # Test package build + if not test_package_build(package_name, build_dir): + package_success = False + + # Test package installation + if not test_package_install(package_name, build_dir): + package_success = False + + if package_success: + success_count += 1 + print(f"✓ {package_name} - ALL TESTS PASSED") + else: + print(f"✗ {package_name} - SOME TESTS FAILED") + + print(f"\n--- Test Summary ---") + print(f"Success: {success_count}/{total_count} packages") + + return success_count == total_count + +def cleanup(): + """Clean up test artifacts""" + print("Cleaning up test artifacts...") + + # Remove test build directory + test_build = Path("test_build") + if test_build.exists(): + import shutil + shutil.rmtree(test_build) + + # Uninstall test packages + packages = [ + "lightspeed-inline-agent", + "lightspeed-agent", + "lightspeed-tool-runtime", + "lightspeed-question-validity", + "lightspeed-redaction" + ] + + for package in packages: + try: + subprocess.run([ + sys.executable, "-m", "pip", "uninstall", package, "-y" + ], capture_output=True) + except: + pass + +def main(): + """Main test function""" + print("Starting package build tests...") + + try: + # Test build script + if not test_build_script(): + print("✗ Build script test failed") + return False + + # Test all packages + if not test_all_packages(): + print("✗ Package tests failed") + return False + + print("\n✓ ALL TESTS PASSED") + return True + + except Exception as e: + print(f"✗ Test error: {e}") + return False + finally: + cleanup() + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) \ No newline at end of file