mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-28 13:26:48 +00:00
416 lines
14 KiB
Markdown
416 lines
14 KiB
Markdown
# Contributing Guide
|
|
|
|
This guide covers how to set up a development environment, run tests, and
|
|
contribute code to OpenJarvis.
|
|
|
|
---
|
|
|
|
## Development Setup
|
|
|
|
### Prerequisites
|
|
|
|
| Requirement | Version | Notes |
|
|
|---|---|---|
|
|
| Python | 3.10+ | Required |
|
|
| [uv](https://docs.astral.sh/uv/) | Latest | Package manager |
|
|
| Node.js | 22+ | Only needed for ClaudeCodeAgent and WhatsApp channel |
|
|
|
|
### Clone and Install
|
|
|
|
```bash
|
|
git clone https://github.com/open-jarvis/OpenJarvis.git
|
|
cd OpenJarvis
|
|
uv sync --extra dev
|
|
```
|
|
|
|
This installs the package in editable mode along with all development
|
|
dependencies (pytest, ruff, respx, pytest-asyncio, pytest-cov).
|
|
|
|
!!! tip "Optional extras"
|
|
Install additional extras for specific backends you want to work on:
|
|
|
|
```bash
|
|
# Memory backends
|
|
uv sync --extra dev --extra memory-faiss --extra memory-colbert --extra memory-bm25
|
|
|
|
# Cloud inference
|
|
uv sync --extra dev --extra inference-cloud --extra inference-google
|
|
|
|
# API server
|
|
uv sync --extra dev --extra server
|
|
|
|
# Documentation
|
|
uv sync --extra dev --extra docs
|
|
```
|
|
|
|
### Verify Installation
|
|
|
|
```bash
|
|
uv run jarvis --version # Should print 0.1.0
|
|
uv run jarvis --help # Show all subcommands
|
|
```
|
|
|
|
---
|
|
|
|
## Running Tests
|
|
|
|
OpenJarvis uses [pytest](https://docs.pytest.org/) with approximately 1,000+
|
|
tests organized by module.
|
|
|
|
### Full Test Suite
|
|
|
|
```bash
|
|
uv run pytest tests/ -v
|
|
```
|
|
|
|
### Run a Specific Test File
|
|
|
|
```bash
|
|
uv run pytest tests/core/test_registry.py -v
|
|
uv run pytest tests/engine/test_ollama.py -v
|
|
uv run pytest tests/memory/test_sqlite.py -v
|
|
```
|
|
|
|
### Run a Specific Test
|
|
|
|
```bash
|
|
uv run pytest tests/core/test_registry.py::test_register_and_get -v
|
|
```
|
|
|
|
### Run Tests by Module
|
|
|
|
```bash
|
|
uv run pytest tests/agents/ -v # All agent tests
|
|
uv run pytest tests/tools/ -v # All tool tests
|
|
uv run pytest tests/learning/ -v # All learning tests
|
|
```
|
|
|
|
### Test Coverage
|
|
|
|
```bash
|
|
uv run pytest tests/ --cov=openjarvis --cov-report=html
|
|
```
|
|
|
|
### Test Markers
|
|
|
|
Tests that require specific hardware or running services are gated behind
|
|
pytest markers. By default, these tests are collected but will skip
|
|
gracefully if the requirement is not met.
|
|
|
|
| Marker | Description | Example |
|
|
|---|---|---|
|
|
| `live` | Requires a running inference engine (Ollama, vLLM, etc.) | `@pytest.mark.live` |
|
|
| `cloud` | Requires cloud API keys (`OPENAI_API_KEY`, etc.) | `@pytest.mark.cloud` |
|
|
| `nvidia` | Requires an NVIDIA GPU | `@pytest.mark.nvidia` |
|
|
| `amd` | Requires an AMD GPU with ROCm | `@pytest.mark.amd` |
|
|
| `apple` | Requires Apple Silicon | `@pytest.mark.apple` |
|
|
| `slow` | Long-running test | `@pytest.mark.slow` |
|
|
|
|
Run only tests matching a specific marker:
|
|
|
|
```bash
|
|
uv run pytest tests/ -m live -v # Only live engine tests
|
|
uv run pytest tests/ -m "not slow" -v # Skip slow tests
|
|
uv run pytest tests/ -m "not cloud" -v # Skip cloud tests
|
|
```
|
|
|
|
!!! info "Registry isolation in tests"
|
|
The test `conftest.py` includes an `autouse` fixture that clears all
|
|
registries and resets the event bus before every test. This ensures
|
|
complete isolation between tests. Modules that need their registrations
|
|
to survive clearing use the `ensure_registered()` pattern described
|
|
below.
|
|
|
|
---
|
|
|
|
## Linting
|
|
|
|
OpenJarvis uses [Ruff](https://docs.astral.sh/ruff/) for linting, configured
|
|
in `pyproject.toml`:
|
|
|
|
```bash
|
|
uv run ruff check src/ tests/
|
|
```
|
|
|
|
The Ruff configuration targets Python 3.10 and enables the following rule sets:
|
|
|
|
- **E** -- pycodestyle errors
|
|
- **F** -- Pyflakes
|
|
- **I** -- isort (import ordering)
|
|
- **W** -- pycodestyle warnings
|
|
|
|
Fix auto-fixable issues:
|
|
|
|
```bash
|
|
uv run ruff check src/ tests/ --fix
|
|
```
|
|
|
|
---
|
|
|
|
## Building Documentation
|
|
|
|
The documentation site uses [MkDocs Material](https://squidfunnel.com/mkdocs-material/).
|
|
|
|
```bash
|
|
# Install docs dependencies
|
|
uv sync --extra docs
|
|
|
|
# Serve locally with hot reload
|
|
uv run mkdocs serve --dev-addr 127.0.0.1:8001
|
|
|
|
# Build static site
|
|
uv run mkdocs build
|
|
```
|
|
|
|
The site configuration lives in `mkdocs.yml`. API reference pages use
|
|
[mkdocstrings](https://mkdocstrings.github.io/) to auto-generate from
|
|
docstrings with the NumPy docstring style.
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
The source code is organized under `src/openjarvis/`:
|
|
|
|
```
|
|
src/openjarvis/
|
|
__init__.py # Package root, __version__
|
|
sdk.py # Jarvis class — high-level Python SDK
|
|
|
|
core/ # Shared infrastructure
|
|
config.py # JarvisConfig, hardware detection, TOML loader
|
|
events.py # EventBus pub/sub system
|
|
registry.py # RegistryBase[T] and all typed registries
|
|
types.py # Message, ModelSpec, ToolResult, Trace, etc.
|
|
|
|
intelligence/ # Model management and query routing
|
|
model_catalog.py # BUILTIN_MODELS, register/merge helpers
|
|
router.py # HeuristicRouter, build_routing_context
|
|
|
|
engine/ # Inference engine backends
|
|
_stubs.py # InferenceEngine ABC
|
|
_base.py # EngineConnectionError, messages_to_dicts
|
|
_discovery.py # discover_engines, discover_models, get_engine
|
|
_openai_compat.py # OpenAI-compatible wrapper
|
|
ollama.py # OllamaEngine
|
|
openai_compat_engines.py # Data-driven registration (vLLM, SGLang, llama.cpp, MLX, LM Studio)
|
|
cloud.py # CloudEngine (OpenAI/Anthropic/Google)
|
|
|
|
agents/ # Agent implementations
|
|
_stubs.py # BaseAgent ABC, ToolUsingAgent, AgentContext, AgentResult
|
|
simple.py # SimpleAgent — single-turn, no tools
|
|
orchestrator.py # OrchestratorAgent — multi-turn tool calling (function_calling + structured)
|
|
native_react.py # NativeReActAgent — Thought-Action-Observation loop
|
|
native_openhands.py # NativeOpenHandsAgent — CodeAct-style code execution
|
|
rlm.py # RLMAgent — recursive LM with persistent REPL
|
|
openhands.py # OpenHandsAgent — wraps real openhands-sdk
|
|
react.py # Backward-compat shim (re-exports NativeReActAgent)
|
|
claude_code.py # ClaudeCodeAgent — Claude Agent SDK via Node.js subprocess
|
|
claude_code_runner/ # Bundled Node.js runner for the Claude Agent SDK
|
|
|
|
memory/ # Memory / retrieval backends
|
|
_stubs.py # MemoryBackend ABC, RetrievalResult
|
|
sqlite.py # SQLiteMemory — FTS5 default backend
|
|
faiss_backend.py # FAISS vector backend
|
|
colbert_backend.py # ColBERTv2 backend
|
|
bm25.py # BM25 backend
|
|
hybrid.py # Hybrid (RRF fusion) backend
|
|
chunking.py # ChunkConfig, chunk_text
|
|
context.py # ContextConfig, inject_context
|
|
ingest.py # ingest_path, read_document
|
|
|
|
tools/ # Tool system
|
|
_stubs.py # BaseTool ABC, ToolSpec, ToolExecutor
|
|
calculator.py # CalculatorTool — safe AST math
|
|
think.py # ThinkTool — reasoning scratchpad
|
|
retrieval.py # RetrievalTool — memory search
|
|
llm_tool.py # LLMTool — sub-model calls
|
|
file_read.py # FileReadTool — safe file reading
|
|
web_search.py # WebSearchTool
|
|
code_interpreter.py # CodeInterpreterTool
|
|
|
|
learning/ # Router policies and reward functions
|
|
_stubs.py # RouterPolicy ABC, RewardFunction ABC
|
|
heuristic_policy.py # Wire HeuristicRouter to registry
|
|
trace_policy.py # TraceDrivenPolicy — learns from traces
|
|
grpo_policy.py # GRPORouterPolicy — RL training stub
|
|
heuristic_reward.py # HeuristicRewardFunction
|
|
|
|
traces/ # Full interaction recording
|
|
store.py # TraceStore — SQLite persistence
|
|
collector.py # TraceCollector — wraps agents
|
|
analyzer.py # TraceAnalyzer — aggregated queries
|
|
|
|
telemetry/ # Inference telemetry
|
|
store.py # TelemetryStore — SQLite persistence
|
|
aggregator.py # TelemetryAggregator — per-model/engine stats
|
|
wrapper.py # instrumented_generate() wrapper
|
|
|
|
bench/ # Benchmarking framework
|
|
_stubs.py # BaseBenchmark ABC, BenchmarkSuite
|
|
latency.py # LatencyBenchmark
|
|
throughput.py # ThroughputBenchmark
|
|
|
|
server/ # OpenAI-compatible API server
|
|
app.py # FastAPI application factory
|
|
routes.py # /v1/chat/completions, /v1/models, /health
|
|
|
|
mcp/ # MCP (Model Context Protocol) layer
|
|
|
|
cli/ # Click CLI commands
|
|
__init__.py # main group
|
|
ask.py # jarvis ask
|
|
init_cmd.py # jarvis init
|
|
model.py # jarvis model list/info
|
|
memory_cmd.py # jarvis memory index/search/stats
|
|
telemetry_cmd.py # jarvis telemetry stats/export/clear
|
|
bench_cmd.py # jarvis bench run
|
|
serve.py # jarvis serve
|
|
```
|
|
|
|
---
|
|
|
|
## Code Conventions
|
|
|
|
### File Naming
|
|
|
|
| Pattern | Purpose | Examples |
|
|
|---|---|---|
|
|
| `_stubs.py` | ABC definitions and dataclasses | `engine/_stubs.py`, `agents/_stubs.py`, `tools/_stubs.py` |
|
|
| `_discovery.py` | Auto-detection and probing logic | `engine/_discovery.py` |
|
|
| `_base.py` | Shared utilities and re-exports | `engine/_base.py` |
|
|
| `*_cmd.py` | CLI command modules | `init_cmd.py`, `memory_cmd.py`, `bench_cmd.py` |
|
|
|
|
### Registry Pattern
|
|
|
|
All extensible components use the decorator-based registry pattern. New
|
|
implementations are added by decorating a class -- no factory modifications
|
|
needed:
|
|
|
|
```python
|
|
from openjarvis.core.registry import EngineRegistry
|
|
|
|
@EngineRegistry.register("my_engine")
|
|
class MyEngine(InferenceEngine):
|
|
...
|
|
```
|
|
|
|
Available registries:
|
|
|
|
| Registry | Stores | Key examples |
|
|
|---|---|---|
|
|
| `ModelRegistry` | `ModelSpec` objects | `"qwen3:8b"`, `"llama3.1:70b"` |
|
|
| `EngineRegistry` | `InferenceEngine` classes | `"ollama"`, `"vllm"`, `"llamacpp"` |
|
|
| `MemoryRegistry` | `MemoryBackend` classes | `"sqlite"`, `"faiss"`, `"bm25"` |
|
|
| `AgentRegistry` | `BaseAgent` classes | `"simple"`, `"orchestrator"` |
|
|
| `ToolRegistry` | `BaseTool` classes | `"calculator"`, `"think"`, `"retrieval"` |
|
|
| `RouterPolicyRegistry` | `RouterPolicy` classes | `"heuristic"`, `"learned"` |
|
|
| `BenchmarkRegistry` | `BaseBenchmark` classes | `"latency"`, `"throughput"` |
|
|
|
|
### Optional Dependencies
|
|
|
|
Backends that depend on optional packages use the `try/except ImportError`
|
|
pattern to fail gracefully when deps are not installed:
|
|
|
|
```python
|
|
# In __init__.py — import to trigger registration
|
|
try:
|
|
import openjarvis.memory.faiss_backend # noqa: F401
|
|
except ImportError:
|
|
pass
|
|
```
|
|
|
|
This ensures the package always loads, even if `faiss-cpu` or other optional
|
|
dependencies are not installed.
|
|
|
|
### The `ensure_registered()` Pattern
|
|
|
|
Benchmark and learning modules use lazy registration so that their entries
|
|
survive registry clearing in tests:
|
|
|
|
```python
|
|
def ensure_registered() -> None:
|
|
"""Register the latency benchmark if not already present."""
|
|
if not BenchmarkRegistry.contains("latency"):
|
|
BenchmarkRegistry.register_value("latency", LatencyBenchmark)
|
|
```
|
|
|
|
This pattern checks `contains()` before registering, making it safe to call
|
|
multiple times without raising a duplicate-key error.
|
|
|
|
### Dataclass Conventions
|
|
|
|
- Use `slots=True` on all dataclasses for memory efficiency:
|
|
|
|
```python
|
|
@dataclass(slots=True)
|
|
class BenchmarkResult:
|
|
benchmark_name: str
|
|
model: str
|
|
...
|
|
```
|
|
|
|
### Type Hints
|
|
|
|
- All function signatures must have type annotations
|
|
- Use `from __future__ import annotations` at the top of every module
|
|
- Use `Optional[X]` for nullable types
|
|
- Use `Sequence` for read-only collections, `List` for mutable ones
|
|
|
|
### Import Style
|
|
|
|
- Absolute imports only (`from openjarvis.core.registry import ...`)
|
|
- Sort imports with `ruff` (isort rules enabled)
|
|
- Place `from __future__ import annotations` as the first import
|
|
|
|
---
|
|
|
|
## PR Guidelines
|
|
|
|
### Before Submitting
|
|
|
|
1. **Run the full test suite** and verify no regressions:
|
|
```bash
|
|
uv run pytest tests/ -v
|
|
```
|
|
|
|
2. **Run the linter** and fix all issues:
|
|
```bash
|
|
uv run ruff check src/ tests/
|
|
```
|
|
|
|
3. **Add tests** for new functionality. Place them in the corresponding
|
|
`tests/` subdirectory (e.g., new engine tests go in `tests/engine/`).
|
|
|
|
4. **Follow the registry pattern** for any new extensible component.
|
|
|
|
### Commit Messages
|
|
|
|
- Use the imperative mood (e.g., "Add FAISS memory backend")
|
|
- Keep the first line under 72 characters
|
|
- Reference relevant issues or PRs
|
|
|
|
### What Makes a Good PR
|
|
|
|
- **Focused**: One feature, fix, or refactor per PR
|
|
- **Tested**: Include unit tests that cover the new code paths
|
|
- **Documented**: Update docstrings and documentation pages if adding
|
|
public API
|
|
- **Backwards compatible**: Avoid breaking existing interfaces without
|
|
discussion
|
|
|
|
### Adding a New Primitive Component
|
|
|
|
When adding a new engine, memory backend, agent, tool, benchmark, or router
|
|
policy:
|
|
|
|
1. Implement the corresponding ABC
|
|
2. Register with the appropriate `@XRegistry.register("key")` decorator
|
|
3. Add an import in the module's `__init__.py` (with `try/except ImportError`
|
|
if the component has optional deps)
|
|
4. Add tests in the matching `tests/` subdirectory
|
|
5. Add an entry in `pyproject.toml` under `[project.optional-dependencies]`
|
|
if the component requires new packages
|
|
|
|
See the [Extending OpenJarvis](extending.md) guide for complete examples.
|