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
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.25.6'


- name: Run go vet
run: go vet ./...

- name: Run tests
run: go test ./...

build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.25.6'


- name: Build
run: go build ./cmd/smart-llama/
85 changes: 85 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Release

on:
push:
tags:
- 'v*.*.*'

permissions:
contents: write

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- goos: linux
goarch: amd64
suffix: linux-amd64
- goos: linux
goarch: arm64
suffix: linux-arm64
- goos: darwin
goarch: amd64
suffix: darwin-amd64
- goos: darwin
goarch: arm64
suffix: darwin-arm64
- goos: windows
goarch: amd64
suffix: windows-amd64
extension: .exe

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.25.6'


- name: Run tests
run: go test ./...

- name: Build binary
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: |
go build -ldflags="-s -w" -o smart-llama-${{ matrix.suffix }}${{ matrix.extension }} ./cmd/smart-llama/

- name: Generate checksum
run: |
sha256sum smart-llama-${{ matrix.suffix }}${{ matrix.extension }} > smart-llama-${{ matrix.suffix }}${{ matrix.extension }}.sha256

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: smart-llama-${{ matrix.suffix }}
path: |
smart-llama-${{ matrix.suffix }}${{ matrix.extension }}
smart-llama-${{ matrix.suffix }}${{ matrix.extension }}.sha256

release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true

- name: List artifacts
run: ls -la artifacts/

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
files: |
artifacts/*
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*.dll
*.so
*.dylib
smart-llama

# Test binary, built with `go test -c`
*.test
Expand Down
197 changes: 197 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# AGENTS.md - Coding Agent Guidelines for smart-llama

This document provides guidance for AI coding agents working in this repository.

## Project Overview

**smart-llama** is a Go server that wraps `llama-server` (llama.cpp) as a subprocess
with dynamic model loading and an OpenAI-compatible API.

## Build & Test Commands

### Build
```bash
# Build the binary
go build ./cmd/smart-llama/

# Build with optimizations (for release)
CGO_ENABLED=0 go build -ldflags="-s -w" ./cmd/smart-llama/
```

### Test
```bash
# Run all tests
go test ./...

# Run tests with verbose output
go test -v ./...

# Run a single test function
go test -v -run TestLoadConfig ./internal/config/

# Run a specific subtest
go test -v -run "TestLoadConfig/valid_config_file" ./internal/config/

# Run tests for a specific package
go test ./internal/process/

# Run tests with coverage
go test -cover ./...
```

### Lint
```bash
# Run go vet (always do this before committing)
go vet ./...

# Format code
go fmt ./...
```

## Project Structure

```
cmd/smart-llama/ # Application entry point
internal/
config/ # YAML configuration loading
process/ # llama-server subprocess management
proxy/ # HTTP reverse proxy to llama-server
server/ # HTTP server and routing
models/ # Model configuration files (*.yaml)
.github/workflows/ # CI and release workflows
```

## Code Style Guidelines

### Imports

Order imports in three groups, separated by blank lines:
1. Standard library
2. External dependencies
3. Internal packages

```go
import (
"context"
"fmt"
"net/http"

"gopkg.in/yaml.v3"

"github.com/fvigneault/smart-llama/internal/config"
)
```

### Naming Conventions

- **Packages**: lowercase, single word (`config`, `process`, `proxy`)
- **Exported types/functions**: PascalCase (`NewManager`, `LoadConfig`)
- **Unexported types/functions**: camelCase (`loadModel`, `killProcessLocked`)
- **Interfaces**: describe behavior, often end in `-er` (`processManager`)
- **Constants**: PascalCase for exported, camelCase for unexported

### Error Handling

- Always wrap errors with context using `fmt.Errorf("description: %w", err)`
- Return early on errors
- Check errors explicitly, never ignore them

```go
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
```

### Struct Tags

Use consistent YAML tags for configuration structs:
```go
type Config struct {
Server ServerConfig `yaml:"server"`
DefaultModel string `yaml:"default_model"`
}
```

### Comments

- Document all exported types and functions
- Use complete sentences starting with the function/type name
- No inline comments unless absolutely necessary

```go
// NewManager creates a new process manager.
func NewManager(...) *Manager {
```

### Concurrency

- Use `sync.RWMutex` for shared state
- Name lock-holding methods with `Locked` suffix (`killProcessLocked`)
- Always use `defer` for unlocking

```go
func (m *Manager) CurrentModel() string {
m.mu.RLock()
defer m.mu.RUnlock()
return m.currentModelValue
}
```

## Testing Guidelines

### Test File Naming
- Test files: `*_test.go` in same package
- Use table-driven tests with subtests

### Test Structure
```go
func TestFunctionName(t *testing.T) {
t.Run("descriptive scenario name", func(t *testing.T) {
// Arrange
// Act
// Assert
})
}
```

### Mocking
- Define interfaces for dependencies (see `processManager` in server.go)
- Create mock implementations in test files
- Use `httptest.NewServer` for HTTP testing

## Configuration

### Global Config (`config.yaml`)
```yaml
server:
listen_addr: ":8080"
llama_server_port: 8081
paths:
llama_server: "/usr/local/bin/llama-server"
models_dir: "./models"
default_model: "model-name"
```

### Model Config (`models/*.yaml`)
```yaml
name: model-name
model_path: /path/to/model.gguf
args:
- --ctx-size
- "8192"
```

## Git Workflow

- Create feature branches: `feature/description`
- Use conventional commits: `feat:`, `fix:`, `docs:`, `test:`, `refactor:`
- Run `go vet ./...` and `go test ./...` before committing
- PR to `main` branch, never commit directly to `main`

## CI/CD

- **CI** (`.github/workflows/ci.yml`): Runs on PRs - `go vet` + `go test`
- **Release** (`.github/workflows/release.yml`): Runs on tags `v*.*.*`
- Builds binaries for Linux/macOS/Windows (amd64/arm64)
- Creates GitHub Release with checksums
Loading