Skip to content

Commit dd1b2ac

Browse files
authored
feat: add quickstart cmd with templates to run (#2374)
## Changes Made <!-- Describe what you changed and why --> - new cli command for quickstart templates - supported templates: rag_eval, agent_evals, benchmark_llm, prompt_evals and workflow_eval | Template | Name | Description | |---|---|---| | rag_eval | RAG Evaluation | Evaluate a RAG system with custom metric | | agent_evals | Agent Evaluation | Evaluate AI agents with structured metrics and workflows | | benchmark_llm | LLM Benchmarking | Benchmark and compare different LLMs with datasets | | prompt_evals | Prompt Evaluation | Evaluate and compare different prompt variations | | workflow_eval | Workflow Evaluation | Evaluate complex LLM workflows and pipeline | ## Testing <!-- Describe how this should be tested --> ### How to Test - [x] Automated tests added/updated - [x] Manual testing steps: 1. `uv run ragas quickstart` 2. `uv run ragas quickstart rag_eval` 3. `uv run ragas quickstart rag_eval --output-dir ./my-project`
1 parent e1e88f7 commit dd1b2ac

File tree

3 files changed

+377
-1
lines changed

3 files changed

+377
-1
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ pip install git+https://github.com/explodinggradients/ragas
7373

7474
## :fire: Quickstart
7575

76+
### Clone a Complete Example Project
77+
78+
The fastest way to get started is to use the `ragas quickstart` command:
79+
80+
```bash
81+
# List available templates
82+
ragas quickstart
83+
84+
# Create a RAG evaluation project
85+
ragas quickstart rag_eval
86+
87+
# Create an agent evaluation project
88+
ragas quickstart agent_evals -o ./my-project
89+
```
90+
91+
Available templates:
92+
- `rag_eval` - Evaluate RAG systems
93+
- `agent_evals` - Evaluate AI agents
94+
- `benchmark_llm` - Benchmark and compare LLMs
95+
- `prompt_evals` - Evaluate prompt variations
96+
- `workflow_eval` - Evaluate complex workflows
97+
7698
### Evaluate your LLM App
7799

78100
This is 5 main lines:

src/ragas/cli.py

Lines changed: 296 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,301 @@ def evals(
457457
raise typer.Exit(1)
458458

459459

460+
@app.command()
461+
def quickstart(
462+
template: Optional[str] = typer.Argument(
463+
None,
464+
help="Template name (e.g., 'rag_eval', 'agent_evals'). Leave empty to see available templates.",
465+
),
466+
output_dir: str = typer.Option(
467+
".", "--output-dir", "-o", help="Directory to create the project in"
468+
),
469+
):
470+
"""
471+
Clone a complete example project to get started with Ragas.
472+
473+
Similar to 'uvx hud-python quickstart', this creates a complete example
474+
project with all necessary files and dependencies.
475+
476+
Examples:
477+
ragas quickstart # List available templates
478+
ragas quickstart rag_eval # Create a RAG evaluation project
479+
ragas quickstart agent_evals -o ./my-project
480+
"""
481+
import shutil
482+
import time
483+
from pathlib import Path
484+
485+
# Define available templates with descriptions
486+
templates = {
487+
"rag_eval": {
488+
"name": "RAG Evaluation",
489+
"description": "Evaluate a RAG (Retrieval Augmented Generation) system with custom metrics",
490+
"source_path": "ragas_examples/rag_eval",
491+
},
492+
"agent_evals": {
493+
"name": "Agent Evaluation",
494+
"description": "Evaluate AI agents with structured metrics and workflows",
495+
"source_path": "ragas_examples/agent_evals",
496+
},
497+
"benchmark_llm": {
498+
"name": "LLM Benchmarking",
499+
"description": "Benchmark and compare different LLM models with datasets",
500+
"source_path": "ragas_examples/benchmark_llm",
501+
},
502+
"prompt_evals": {
503+
"name": "Prompt Evaluation",
504+
"description": "Evaluate and compare different prompt variations",
505+
"source_path": "ragas_examples/prompt_evals",
506+
},
507+
"workflow_eval": {
508+
"name": "Workflow Evaluation",
509+
"description": "Evaluate complex LLM workflows and pipelines",
510+
"source_path": "ragas_examples/workflow_eval",
511+
},
512+
}
513+
514+
# If no template specified, list available templates
515+
if template is None:
516+
console.print(
517+
"\n[bold cyan]Available Ragas Quickstart Templates:[/bold cyan]\n"
518+
)
519+
520+
# Create a table of templates
521+
table = Table(show_header=True, header_style="bold yellow")
522+
table.add_column("Template", style="cyan", no_wrap=True)
523+
table.add_column("Name", style="green")
524+
table.add_column("Description", style="white")
525+
526+
for template_id, template_info in templates.items():
527+
table.add_row(
528+
template_id, template_info["name"], template_info["description"]
529+
)
530+
531+
console.print(table)
532+
console.print("\n[bold]Usage:[/bold]")
533+
console.print(" ragas quickstart [template_name]")
534+
console.print("\n[bold]Example:[/bold]")
535+
console.print(" ragas quickstart rag_eval")
536+
console.print(" ragas quickstart agent_evals --output-dir ./my-project\n")
537+
return
538+
539+
# Validate template name
540+
if template not in templates:
541+
error(f"Unknown template: {template}")
542+
console.print(f"\nAvailable templates: {', '.join(templates.keys())}")
543+
console.print("Run 'ragas quickstart' to see all available templates.")
544+
raise typer.Exit(1)
545+
546+
template_info = templates[template]
547+
template_path = template_info["source_path"].replace("ragas_examples/", "")
548+
549+
# Try to find examples locally first (for development and testing)
550+
# Look for examples in the installed ragas-examples package or local dev environment
551+
source_path = None
552+
temp_dir = None
553+
554+
try:
555+
import ragas_examples
556+
557+
if ragas_examples.__file__ is not None:
558+
examples_root = Path(ragas_examples.__file__).parent
559+
local_source = examples_root / template_path
560+
if local_source.exists():
561+
source_path = local_source
562+
info("Using locally installed examples")
563+
except ImportError:
564+
pass
565+
566+
# If not found locally, check if we're in the ragas repository (dev mode)
567+
if source_path is None:
568+
# Try to find examples directory relative to this file (development mode)
569+
cli_file = Path(__file__).resolve()
570+
repo_root = cli_file.parent.parent.parent # Go up from src/ragas/cli.py
571+
local_examples = repo_root / "examples" / "ragas_examples" / template_path
572+
if local_examples.exists():
573+
source_path = local_examples
574+
info("Using local development examples")
575+
576+
# If still not found, download from GitHub
577+
if source_path is None:
578+
import tempfile
579+
import urllib.request
580+
import zipfile
581+
582+
github_repo = "explodinggradients/ragas"
583+
branch = "main"
584+
585+
# Create temporary directory for download
586+
temp_dir = Path(tempfile.mkdtemp())
587+
588+
try:
589+
# Download the specific template folder from GitHub
590+
archive_url = (
591+
f"https://github.com/{github_repo}/archive/refs/heads/{branch}.zip"
592+
)
593+
594+
with Live(
595+
Spinner(
596+
"dots", text="Downloading template from GitHub...", style="cyan"
597+
),
598+
console=console,
599+
):
600+
zip_path = temp_dir / "repo.zip"
601+
urllib.request.urlretrieve(archive_url, zip_path)
602+
603+
with zipfile.ZipFile(zip_path, "r") as zip_ref:
604+
zip_ref.extractall(temp_dir)
605+
606+
extracted_folders = [
607+
f
608+
for f in temp_dir.iterdir()
609+
if f.is_dir() and f.name.startswith("ragas-")
610+
]
611+
if not extracted_folders:
612+
error("Failed to extract template from GitHub archive")
613+
raise typer.Exit(1)
614+
615+
repo_dir = extracted_folders[0]
616+
source_path = repo_dir / "examples" / "ragas_examples" / template_path
617+
618+
if not source_path.exists():
619+
error(f"Template not found in repository: {template_path}")
620+
console.print(f"Looking for: {source_path}")
621+
raise typer.Exit(1)
622+
623+
except Exception as e:
624+
error(f"Failed to download template from GitHub: {e}")
625+
console.print("\nYou can also manually clone the repository:")
626+
console.print(f" git clone https://github.com/{github_repo}.git")
627+
console.print(
628+
f" cp -r ragas/examples/ragas_examples/{template_path} ./{template}"
629+
)
630+
raise typer.Exit(1)
631+
632+
# Determine output directory
633+
output_path = Path(output_dir) / template
634+
635+
if output_path.exists():
636+
warning(f"Directory already exists: {output_path}")
637+
overwrite = typer.confirm("Do you want to overwrite it?", default=False)
638+
if not overwrite:
639+
info("Operation cancelled.")
640+
raise typer.Exit(0)
641+
shutil.rmtree(output_path)
642+
643+
# Copy the template
644+
with Live(
645+
Spinner(
646+
"dots", text=f"Creating {template_info['name']} project...", style="green"
647+
),
648+
console=console,
649+
) as live:
650+
live.update(Spinner("dots", text="Copying template files...", style="green"))
651+
shutil.copytree(source_path, output_path)
652+
time.sleep(0.3)
653+
654+
live.update(
655+
Spinner("dots", text="Setting up project structure...", style="green")
656+
)
657+
658+
evals_dir = output_path / "evals"
659+
evals_dir.mkdir(exist_ok=True)
660+
(evals_dir / "datasets").mkdir(exist_ok=True)
661+
(evals_dir / "experiments").mkdir(exist_ok=True)
662+
(evals_dir / "logs").mkdir(exist_ok=True)
663+
time.sleep(0.2)
664+
665+
# Create a README.md with setup instructions
666+
live.update(Spinner("dots", text="Creating documentation...", style="green"))
667+
readme_content = f"""# {template_info["name"]}
668+
669+
{template_info["description"]}
670+
671+
## Setup
672+
673+
1. Set your OpenAI API key (or other LLM provider):
674+
```bash
675+
export OPENAI_API_KEY="your-api-key"
676+
```
677+
678+
2. Install dependencies:
679+
```bash
680+
pip install ragas openai
681+
```
682+
683+
## Running the Example
684+
685+
Run the evaluation:
686+
```bash
687+
python app.py
688+
```
689+
690+
Or run via the CLI:
691+
```bash
692+
ragas evals evals/evals.py --dataset test_data --metrics [metric_names]
693+
```
694+
695+
## Project Structure
696+
697+
```
698+
{template}/
699+
├── app.py # Your application code (RAG system, agent, etc.)
700+
├── evals/ # Evaluation-related code and data
701+
│ ├── evals.py # Evaluation metrics and experiment definitions
702+
│ ├── datasets/ # Test datasets
703+
│ ├── experiments/ # Experiment results
704+
│ └── logs/ # Evaluation logs and traces
705+
└── README.md
706+
```
707+
708+
This structure separates your application code from evaluation code, making it easy to:
709+
- Develop and test your application independently
710+
- Run evaluations without mixing concerns
711+
- Track evaluation results separately from application logic
712+
713+
## Next Steps
714+
715+
1. Implement your application logic in `app.py`
716+
2. Review and modify the metrics in `evals/evals.py`
717+
3. Customize the dataset in `evals/datasets/`
718+
4. Run experiments and analyze results
719+
5. Iterate on your prompts and system design
720+
721+
## Documentation
722+
723+
Visit https://docs.ragas.io for more information.
724+
"""
725+
726+
readme_path = output_path / "README.md"
727+
with open(readme_path, "w", encoding="utf-8") as f:
728+
f.write(readme_content)
729+
time.sleep(0.2)
730+
731+
live.update(Spinner("dots", text="Finalizing project...", style="green"))
732+
time.sleep(0.3)
733+
734+
# Cleanup temporary directory if we downloaded from GitHub
735+
if temp_dir is not None:
736+
try:
737+
shutil.rmtree(temp_dir)
738+
except Exception:
739+
pass
740+
741+
# Success message with next steps
742+
success(f"\n✓ Created {template_info['name']} project at: {output_path}")
743+
console.print("\n[bold cyan]Next Steps:[/bold cyan]")
744+
console.print(f" 1. cd {output_path}")
745+
console.print(" 2. export OPENAI_API_KEY='your-api-key'")
746+
console.print(" 3. pip install ragas openai")
747+
console.print(" 4. python app.py")
748+
console.print("\n[bold]Project Structure:[/bold]")
749+
console.print(" app.py - Your application code")
750+
console.print(" evals/ - All evaluation-related code and data")
751+
console.print("\n[bold]Quick Start:[/bold]")
752+
console.print(f" cd {output_path} && python app.py\n")
753+
754+
460755
@app.command()
461756
def hello_world(
462757
directory: str = typer.Argument(
@@ -610,7 +905,7 @@ async def run_experiment(row: TestDataRow):
610905
'''
611906

612907
evals_path = os.path.join(directory, "hello_world", "evals.py")
613-
with open(evals_path, "w") as f:
908+
with open(evals_path, "w", encoding="utf-8") as f:
614909
f.write(evals_content)
615910
time.sleep(0.5) # Brief pause to show spinner
616911

0 commit comments

Comments
 (0)