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
127 changes: 120 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ It's repetitive. It's inconsistent. And you haven't written a single line of *re

| Feature | What it does |
|---|---|
| **Intent-based templates** | Backend API (FastAPI / Flask / Django), CLI Application (Typer / Click / Argparse), Automation Tool, AI Chatbot |
| **Intent-based templates** | Backend API (FastAPI / Flask / Django), CLI Application (Typer / Click / Argparse), Automation Tool, AI Chatbot, AI Agent, RAG System |
| **Extras system** | Opt-in ruff, pytest, Docker, GitHub Actions — installed and wired automatically |
| **Dependency installation** | `uv add` runs automatically with the right packages for your choices |
| **Git + uv** | Optionally runs `git init`, `uv init`, and `uv venv` |
Expand Down Expand Up @@ -92,8 +92,10 @@ Spawn rejects names with spaces or special characters, and tells you immediately
2 CLI Application
3 Automation Tool
4 AI Chatbot
5 AI Agent
6 RAG System

Choose Template [1-4]: 1
Choose Template [1-6]: 1
```

**Step 3 — Additional prompts** *(template-dependent)*
Expand Down Expand Up @@ -284,7 +286,8 @@ my-chatbot/
│ ├── chatbot/
│ ├── providers/
│ ├── prompts/
│ ├── utils/
│ ├── memory/
│ ├── config/
│ └── main.py
├── tests/
├── .env.example
Expand All @@ -293,10 +296,120 @@ my-chatbot/

```bash
cd my-chatbot
# Add API_KEY to .env
# Add API key to .env
uv run python -m src.main
```

**AI Chatbot — Framework and provider**

```
1 pydantic-ai
2 openai-sdk
3 litellm

Choose Framework [1-3]: 1

1 openai
2 anthropic
3 gemini
4 openrouter
5 ollama
6 groq

Choose Provider [1-6]: 1
```

---

### `[5]` AI Agent

Best for: task automation, research assistants, tool-calling workflows.

```
my-agent/
├── src/
│ ├── agent/
│ ├── tools/
│ ├── prompts/
│ ├── config/
│ └── main.py
├── tests/
├── .env.example
└── README.md
```

```bash
cd my-agent
# Add API key to .env
uv run python -m src.main
```

**AI Agent — Framework and provider**

```
1 pydantic-ai
2 openai-agents

Choose Framework [1-2]: 1

1 openai
2 anthropic
3 gemini
4 openrouter
5 ollama
6 groq

Choose Provider [1-6]: 1
```

Note: if you pick `openai-agents`, only `openai` and `openrouter` appear as provider choices — the list is filtered per framework.

Every generated AI Agent project ships with one working tool (a calculator) so you can see tool-calling work immediately, with no external services or extra API keys required beyond your chosen provider.

---

### `[6]` RAG System

Best for: documentation Q&A, knowledge base search, document retrieval.

```
my-rag/
├── data/
│ └── sample_knowledge.md
├── src/
│ ├── knowledge/
│ ├── ingestion/
│ ├── retrieval/
│ ├── config/
│ └── main.py
├── tests/
├── chroma_db/ # created on first run
├── .env.example
└── README.md
```

```bash
cd my-rag
# Add OPENAI_API_KEY to .env
uv run python -m src.main
# Documents are ingested automatically on first run
```

**RAG System — Extras only, no framework/provider prompt**

```
1 ruff
2 pytest
3 github-actions

Enter numbers separated by commas, or press Enter to skip
Extras []: 1,2
```

RAG System uses a fixed stack (LlamaIndex + ChromaDB + OpenAI) — there's no framework or provider choice. On first run, it automatically ingests documents from `data/` into a local ChromaDB index, then lets you ask questions against them.

Requires an `OPENAI_API_KEY` (used for both the LLM and embeddings).

---

## Other Commands
Expand Down Expand Up @@ -341,7 +454,7 @@ spawn doctor ./path/to/project

```bash
spawn version
# → Spawn v0.6.0
# → Spawn v0.8.0
```

### Publish to GitHub
Expand Down Expand Up @@ -378,8 +491,8 @@ All tests should pass. If they don't, please [open an issue](https://github.com/
- [x] **CLI Application intent** — Typer, Click, Argparse with Utility/Interactive sub-types (v0.4.0)
- [x] **Automation Tool intent** — workflow-based automation with logging, tasks, and integrations (v0.5.0)
- [x] **AI Chatbot intent** — PydanticAI and OpenAI SDK with provider abstraction (v0.6.0)
- [ ] **AI Agent intent** — tool-calling agent scaffold (v0.7.0)
- [ ] **RAG System intent** — retrieval-augmented generation scaffold (v0.8.0)
- [x] **AI Agent intent** — tool-calling agent scaffold with PydanticAI and OpenAI Agents SDK (v0.7.0)
- [x] **RAG System intent** — retrieval-augmented generation with LlamaIndex and ChromaDB (v0.8.0)
- [ ] **Data Project intent** — analysis, dashboard, ETL, ML sub-options (v0.9.0)

---
Expand Down
35 changes: 35 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,41 @@
All notable changes to Spawn are documented here.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## v0.8.0 — 2026

### New Features

- **RAG System intent** — generates a fully runnable Retrieval-Augmented Generation
project using a fixed stack: LlamaIndex + ChromaDB + OpenAI
- **Fixed stack, no framework/provider prompt** — RAG skips the framework/provider
selection entirely; `spawn create` goes straight from intent to extras
- **Auto-ingestion on first run** — if `chroma_db/` is empty, `src/main.py` ingests
everything in `data/` automatically before accepting questions; no separate ingest
command required
- **Sample knowledge base** — every generated project ships with
`data/sample_knowledge.md` so users get a working Q&A demo immediately, with zero
setup beyond an API key
- **Local vector persistence** — `src/knowledge/index.py` owns a
`chromadb.PersistentClient` writing to `chroma_db/`, shared by both ingestion and
retrieval
- **Correct modern LlamaIndex packaging** — installs `llama-index-core`,
`llama-index-vector-stores-chroma`, `llama-index-embeddings-openai`,
`llama-index-llms-openai`, and `chromadb` explicitly, instead of the heavy
`llama-index` meta-package
- **`chroma_db/` gitignored correctly** — binary ChromaDB files are excluded via
`.gitignore`, while a `.gitkeep` keeps the directory tracked in git

### Internal

- Registry: `rag` slug added with `available_extras` only — no `available_frameworks`
or `available_providers`
- `RAGTemplate` follows the same `BaseTemplate` contract as other intents
(`get_dependencies()`, `get_readme_content()`, `post_install()`, `next_steps`)
- `prompts.py` required no changes — empty `available_frameworks` and
`available_providers` already skip those prompts automatically
- `shared_content.py`'s `GITIGNORE_CONTENT` extended with a `chroma_db/` rule
- Version bumped to `0.8.0`

## v0.7.0 — 2026

### New Features
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "spawn"
version = "0.7.0"
version = "0.8.0"
description = "Spawn a ready-to-use Python project in seconds"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
2 changes: 1 addition & 1 deletion src/spawn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
try:
__version__ = version("spawn")
except PackageNotFoundError:
__version__ = "0.7.0"
__version__ = "0.8.0"
8 changes: 8 additions & 0 deletions src/spawn/core/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from spawn.templates.automation import AutomationTemplate
from spawn.templates.chatbot import ChatbotTemplate
from spawn.templates.agent import AgentTemplate
from spawn.templates.rag import RAGTemplate
from spawn.templates.base import BaseTemplate


Expand Down Expand Up @@ -74,6 +75,13 @@ class TemplateMetadata:
available_providers=["openai", "anthropic", "gemini", "openrouter", "ollama", "groq"],
available_extras=["ruff", "pytest", "github-actions"],
),
"rag": TemplateMetadata(
slug="rag",
display_name="RAG System",
description="Retrieval-Augmented Generation with LlamaIndex and ChromaDB",
template_class=RAGTemplate,
available_extras=["ruff", "pytest", "github-actions"],
),
}


Expand Down
120 changes: 120 additions & 0 deletions src/spawn/templates/rag/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from pathlib import Path

from spawn.templates.base import BaseTemplate
from spawn.templates.rag.content import (
INIT_CONTENT,
SAMPLE_KNOWLEDGE_CONTENT,
SETTINGS_CONTENT,
KNOWLEDGE_INDEX_CONTENT,
INGESTION_CONTENT,
RETRIEVAL_CONTENT,
MAIN_CONTENT,
TEST_CONTENT,
CONFTEST_CONTENT,
ENV_CONTENT,
GITHUB_ACTIONS_CI_BASE,
GITHUB_ACTIONS_CI_RUFF_STEP,
GITHUB_ACTIONS_CI_PYTEST_STEP,
make_readme,
)

RAG_FOLDERS = [
"data",
"chroma_db",
"src/ingestion",
"src/retrieval",
"src/knowledge",
"src/config",
"tests",
]


def _build_files() -> list:
return [
("data/sample_knowledge.md", SAMPLE_KNOWLEDGE_CONTENT),
("src/__init__.py", INIT_CONTENT),
("src/config/__init__.py", INIT_CONTENT),
("src/config/settings.py", SETTINGS_CONTENT),
("src/knowledge/__init__.py", INIT_CONTENT),
("src/knowledge/index.py", KNOWLEDGE_INDEX_CONTENT),
("src/ingestion/__init__.py", INIT_CONTENT),
("src/ingestion/ingest.py", INGESTION_CONTENT),
("src/retrieval/__init__.py", INIT_CONTENT),
("src/retrieval/retrieve.py", RETRIEVAL_CONTENT),
("src/main.py", MAIN_CONTENT),
("tests/__init__.py", INIT_CONTENT),
("tests/conftest.py", CONFTEST_CONTENT),
("tests/test_rag.py", TEST_CONTENT),
(".env.example", ENV_CONTENT),
]


class RAGTemplate(BaseTemplate):
def __init__(self, extras: list[str] | None = None) -> None:
self.extras = extras or []

super().__init__(
name="RAG System",
folders=list(RAG_FOLDERS),
starter_files=_build_files(),
next_steps=[
"cd {project_name}",
"Rename .env.example to .env and add your OPENAI_API_KEY",
"uv run python -m src.main",
],
)

def get_readme_content(self, context: dict) -> str | None:
return make_readme().format_map(context)

def get_dependencies(self) -> list[str]:
base = [
"llama-index-core",
"llama-index-vector-stores-chroma",
"llama-index-embeddings-openai",
"llama-index-llms-openai",
"chromadb",
"python-dotenv",
]
if "pytest" in self.extras:
base.append("pytest")
if "ruff" in self.extras:
base.append("ruff")
return base

def post_install(self, project_path: Path) -> None:
pyproject = project_path / "pyproject.toml"
current = pyproject.read_text(encoding="utf-8")
additions = ""

if "pytest" in self.extras:
additions += "\n[tool.pytest.ini_options]\ntestpaths = [\"tests\"]\n"

if "ruff" in self.extras:
additions += "\n[tool.ruff]\nline-length = 88\n"

if additions:
pyproject.write_text(current + additions, encoding="utf-8")

if "github-actions" in self.extras:
workflows_path = project_path / ".github" / "workflows"
workflows_path.mkdir(parents=True, exist_ok=True)
ci = GITHUB_ACTIONS_CI_BASE
if "ruff" in self.extras:
ci += GITHUB_ACTIONS_CI_RUFF_STEP
if "pytest" in self.extras:
ci += GITHUB_ACTIONS_CI_PYTEST_STEP
(workflows_path / "ci.yml").write_text(ci, encoding="utf-8")

# Write .gitkeep into chroma_db/ so git tracks the directory
# but the actual DB files are gitignored
gitkeep = project_path / "chroma_db" / ".gitkeep"
gitkeep.write_text("", encoding="utf-8")

gitignore_path = project_path / ".gitignore"
current_gitignore = gitignore_path.read_text(encoding="utf-8")
rag_ignore_rules = "\n# RAG vector database\nchroma_db/*\n!chroma_db/.gitkeep\n"
if "chroma_db/*" not in current_gitignore:
gitignore_path.write_text(
current_gitignore + rag_ignore_rules, encoding="utf-8"
)
Loading
Loading