Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[env]
RUST_TEST_THREADS = { value = "8", force = true }
177 changes: 177 additions & 0 deletions .dingllm/prds/v3/000_remove_python.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# v3: Remove Python Implementation, Promote Rust to Root

## Goal

Remove the Python implementation entirely and move the Rust implementation from `rust/` to the repository root. After this change, `Cargo.toml` lives at root, `src/` contains Rust source, and no Python packaging/test infrastructure remains.

## Motivation

The Rust port (v2) is complete and CI-tested. The Python implementation is no longer maintained — it has no CI, and the README already points users to the Rust build. Keeping both adds confusion for contributors and agents navigating the repo.

## Plan

### 1. Remove Python files

Delete these tracked paths:

| Path | What it is |
|------|-----------|
| `src/buzzllm/` | Python source (main.py, llm.py, prompts/, tools/) |
| `tests/` | Python test suite (conftest.py, unit/, integration/, e2e/) |
| `pyproject.toml` | Python packaging config |
| `.python-version` | Pins Python 3.10 for uv |
| `uv.lock` | uv lockfile |
| `python_runtime_docker/` | Docker build for Python exec tool |

Note: `.venv/`, `build/`, `__pycache__/` are gitignored and not tracked.

### 2. Move Rust to root

Move these from `rust/` to repo root:

| From | To |
|------|-----|
| `rust/Cargo.toml` | `Cargo.toml` |
| `rust/Cargo.lock` | `Cargo.lock` |
| `rust/src/` | `src/` |
| `rust/tests/` | `tests/` |
| `rust/.cargo/` | `.cargo/` |

Delete `rust/.gitignore` (its content — `target/` — merges into root `.gitignore`).

Use `git mv` for each to preserve history.

### 3. Update CI

`.github/workflows/ci.yml` — remove `working-directory: rust` from all 4 jobs (build, test, clippy, fmt) and the `workspaces: rust` from rust-cache config.

Before:
```yaml
- uses: Swatinem/rust-cache@v2
with:
workspaces: rust
- name: Build (debug)
run: cargo build
working-directory: rust
```

After:
```yaml
- uses: Swatinem/rust-cache@v2
- name: Build (debug)
run: cargo build
```

### 4. Update `.gitignore`

Replace Python-centric patterns with Rust:

```gitignore
# Rust
target/

# Misc
.DS_Store
dev_scratch
.dingllm/files.txt
llm.md
```

### 5. Update `AGENTS.md`

- Remove "Two implementations" framing
- Remove Python quick-reference section
- Remove Python-specific gotchas (global state, conftest, asyncio_mode)
- Remove Python layout entries
- Keep Rust commands, gotchas, and layout (updated paths: `src/` not `rust/src/`)
- Keep "Adding a new provider" / "Adding a new tool" recipes (updated for root paths)

### 6. Update `CLAUDE.md`

Remove or replace. Current content is mostly Python-focused (CLI examples, Python architecture, Python data flow). Options:

- **(a) Delete** — the README and AGENTS.md already cover everything an agent needs.
- **(b) Rewrite** — trim to Rust-only architecture reference.

Recommendation: **(a) Delete**. The README has full CLI usage, AGENTS.md has dev workflow. A third file adds maintenance burden with no new information.

### 7. Update `README.md`

Minimal changes:
- Install section: `cd buzzllm/rust` becomes `cd buzzllm`
- Architecture section: paths drop the `rust/` prefix (already shows `src/` without prefix)
- Python test suite reference section: remove entirely

## Files changed (summary)

| Action | Path |
|--------|------|
| DELETE | `src/buzzllm/` (Python source) |
| DELETE | `tests/` (Python tests) |
| DELETE | `pyproject.toml` |
| DELETE | `.python-version` |
| DELETE | `uv.lock` |
| DELETE | `python_runtime_docker/` |
| DELETE | `CLAUDE.md` |
| DELETE | `rust/` (after moving contents) |
| MOVE | `rust/Cargo.toml` -> `Cargo.toml` |
| MOVE | `rust/Cargo.lock` -> `Cargo.lock` |
| MOVE | `rust/src/` -> `src/` |
| MOVE | `rust/tests/` -> `tests/` |
| MOVE | `rust/.cargo/` -> `.cargo/` |
| EDIT | `.github/workflows/ci.yml` |
| EDIT | `.gitignore` |
| EDIT | `AGENTS.md` |
| EDIT | `README.md` |

## Acceptance criteria

1. `cargo build` works from repo root
2. `cargo test -- --test-threads=1` passes from repo root
3. `cargo clippy -- -D warnings` passes
4. `cargo fmt -- --check` passes
5. CI workflow runs successfully (no `working-directory` references to `rust/`)
6. No Python files remain in tracked tree (`*.py`, `pyproject.toml`, etc.)
7. `git log --follow src/main.rs` shows history through the move

## Execution order

```
git stash # save current uncommitted work
git checkout -b remove-python-implementation

# Step 1: remove Python
git rm -r src/buzzllm tests pyproject.toml .python-version uv.lock python_runtime_docker

# Step 2: move Rust to root (git mv preserves history)
git mv rust/Cargo.toml Cargo.toml
git mv rust/Cargo.lock Cargo.lock
git mv rust/src src
git mv rust/tests tests
git mv rust/.cargo .cargo
git rm rust/.gitignore
rmdir rust # should be empty now

# Step 3-7: edit CI, .gitignore, AGENTS.md, README.md, delete CLAUDE.md
# ... (see sections above)

git add -A
git commit -m "refactor: remove Python implementation, promote Rust to repo root

src/buzzllm/, tests/, pyproject.toml, .python-version, uv.lock, python_runtime_docker/
- Remove entire Python implementation (source, tests, packaging, Docker build)
- Move rust/ contents to repo root (Cargo.toml, src/, tests/, .cargo/)
- Update CI to remove working-directory: rust
- Update .gitignore for Rust-only repo
- Update AGENTS.md and README.md for new layout
- Delete CLAUDE.md (redundant with README + AGENTS.md)"

git stash pop # restore uncommitted work
```

## Not in scope

- Adding new Rust features
- Changing the Rust code itself (only file moves)
- Modifying Cargo.toml dependencies or configuration
- Touching `.dingllm/specs/` diagrams (they already reference the Rust layout)
11 changes: 0 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,10 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: rust
- name: Build (debug)
run: cargo build
working-directory: rust
- name: Build (release)
run: cargo build --release
working-directory: rust

test:
name: Test
Expand All @@ -34,13 +30,10 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: rust
- name: Install system deps
run: sudo apt-get update && sudo apt-get install -y ripgrep
- name: Run tests
run: cargo test -- --test-threads=1
working-directory: rust
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

Expand All @@ -53,11 +46,8 @@ jobs:
with:
components: clippy
- uses: Swatinem/rust-cache@v2
with:
workspaces: rust
- name: Run clippy
run: cargo clippy -- -D warnings
working-directory: rust

fmt:
name: Format
Expand All @@ -69,4 +59,3 @@ jobs:
components: rustfmt
- name: Check formatting
run: cargo fmt -- --check
working-directory: rust
13 changes: 4 additions & 9 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Rust
target/

# Virtual environments
.venv
# Misc
.DS_Store
dev_scratch
.dingllm/files.txt
llm.md
1 change: 0 additions & 1 deletion .python-version

This file was deleted.

84 changes: 46 additions & 38 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,61 +1,69 @@
# AGENTS.md

See `CLAUDE.md` for full architecture docs, provider system, and CLI usage examples.

## Quick reference

```bash
# Install
uv venv -p 3.10 && source .venv/bin/activate && uv pip install .

# Install with test deps
uv pip install -e ".[test]"
# Build
cargo build # debug
cargo build --release # release (~8.8 MB binary)

# Run tests
uv run pytest tests/unit -v # unit (no network/docker needed)
uv run pytest tests/integration -v # needs OPENAI_API_KEY / ANTHROPIC_API_KEY
uv run pytest tests/e2e -v # CLI smoke tests
# Test (single-threaded required)
cargo test -- --test-threads=1

# Python exec feature requires docker container
cd python_runtime_docker && bash build_docker.sh build-python-exec && cd ..
# Lint and format
cargo clippy -- -D warnings # CI treats warnings as errors
cargo fmt -- --check # format check
```

CI runs all four checks: build, test, clippy, fmt (`.github/workflows/ci.yml`).

## Key gotchas

- **Global mutable state**: `utils.AVAILABLE_TOOLS` and `llm.TOOL_CALLS` are module-level dicts. The `conftest.py` autouse fixture `reset_tool_state` clears them between tests. If you add new global state to `llm.py` or `tools/`, add cleanup to that fixture.
- **Logs go to `/tmp/buzzllm.logs`**, not stdout. `logger.remove()` is called at import time in `main.py:4`, so loguru never writes to stderr.
- **asyncio_mode = "auto"** in pytest config -- no need to decorate async tests with `@pytest.mark.asyncio`.
- **Tool schema generation** derives from function docstrings + type hints via `callable_to_*_schema()` in `tools/utils.py`. Functions registered as tools **must** have a docstring with `:param` entries or schema generation will break.
- **Tests require `--test-threads=1`** — `set_current_dir` is process-global; parallel tests corrupt each other's CWD. CI enforces this.
- **Logs go to `/tmp/buzzllm.logs`**, not stdout. Controlled via `RUST_LOG` env var (e.g. `RUST_LOG=buzzllm=debug`).
- **Tool schema generation** uses the `Tool` trait's `openai_schema()` / `anthropic_schema()` methods. Each tool struct must implement both.
- **Prompt templates** are compiled in via `include_str!` in `src/prompts/mod.rs`. Adding a prompt means adding both a `.txt` file and a match arm in `get_prompt()`.
- **`pythonexec` tool needs Docker** — build the image first: `cd python_runtime_docker && bash build_docker.sh build-python-exec`
- **`codesearch` tools require `rg` (ripgrep)** — `brew install ripgrep` / `apt install ripgrep`.
- **`.cargo/config.toml`** sets `RUST_TEST_THREADS=8` (overridden by explicit `--test-threads=1` on CLI).

## Layout

```
src/buzzllm/
main.py # CLI entrypoint, arg parsing, tool registration, provider dispatch
llm.py # LLMOptions/RequestArgs/StreamResponse dataclasses, all provider
# make_*/handle_*/tool_call_response_to_* functions, invoke_llm loop
prompts/ # system prompt templates keyed by name (websearch, codesearch, etc.)
src/
main.rs CLI (clap) + prompt resolution + tool registration
llm.rs invoke_llm() streaming loop, SSE parsing, tool execution
types.rs LlmOptions, RequestArgs, StreamResponse, ToolCallData
output.rs Colored stdout or SSE event format
lib.rs Crate root, re-exports
providers/
mod.rs LlmClient trait + create_client() factory
openai_chat.rs /v1/chat/completions
openai_responses.rs /v1/responses
anthropic.rs /v1/messages
vertexai_anthropic.rs GCP Vertex AI (delegates SSE to anthropic)
tools/
utils.py # AVAILABLE_TOOLS registry, add_tool(), callable_to_*_schema()
websearch.py # search_web (DuckDuckGo + Brave fallback), scrape_webpage (crawl4ai)
codesearch.py # bash_find, bash_ripgrep, bash_read
pythonexec.py # python_execute (Docker container on port 8787)

tests/
conftest.py # shared fixtures, global state reset, skip markers
unit/ # fast, no network/docker
integration/ # needs real API keys in env
e2e/ # CLI subprocess tests
mod.rs Tool trait + ToolRegistry
codesearch.rs BashFind, BashRipgrep, BashRead
websearch.rs SearchWeb (DDG + Brave fallback), ScrapeWebpage
pythonexec.rs PythonExecute (Docker via bollard)
write_file.rs WriteFile (exact string replace)
bash.rs Bash (arbitrary shell commands)
prompts/
mod.rs get_prompt() + prompt_names()
*.txt 7 prompt templates (include_str! at compile time)

tests/ Integration/e2e tests (cargo test)
```

## Adding a new provider

1. Add `make_<name>_request_args()`, `handle_<name>_stream_response()`, and `tool_call_response_to_<name>_messages()` in `llm.py`
2. Add entry to `provider_map` dict in `main.py:chat()` (~line 87)
3. Add the provider name to `--provider` choices in `parse_args()` (~line 40)
1. Create `src/providers/<name>.rs` — implement `LlmClient` trait
2. Add the struct + match arm in `create_client()` in `src/providers/mod.rs`
3. Add the provider name to `value_parser` in `src/main.rs` Cli struct

## Adding a new tool

1. Create the callable in the appropriate `tools/*.py` file (must have typed params + docstring with `:param`)
2. Register with `utils.add_tool(fn)` in the relevant `elif` branch of `main.py:chat()`
3. Add `callable_to_schema(utils.AVAILABLE_TOOLS["name"])` to the tools list in the same branch
1. Create a struct implementing `Tool` trait in `src/tools/<name>.rs`
2. Register it in the relevant prompt branch in `src/main.rs`
3. Add `mod <name>;` to `src/tools/mod.rs`
Loading
Loading