viraj.kothari
fix: rename agent folders to remove spaces
58b74a0
Raw
History Blame Contribute Delete
7.5 kB
"""
cli.py β€” Command-line interface for the Knowledge Agent
=======================================================
Usage examples:
python cli.py index # index ./documents/
python cli.py index --dir ~/my_docs # index a custom folder
python cli.py index --force # force re-index everything
python cli.py ask "What is our pricing?" # one-shot Q&A
python cli.py chat # interactive chat loop
python cli.py list # list indexed documents
python cli.py stats # show DB stats
python cli.py delete report.pdf # remove a document
"""
import argparse
import sys
import os
from typing import List, Dict
# ── Pretty printing helpers ───────────────────────────────────────────────────
try:
from rich.console import Console
from rich.markdown import Markdown
from rich.table import Table
from rich.panel import Panel
from rich import print as rprint
RICH = True
console = Console()
except ImportError:
RICH = False
console = None
from knowledge_api import (
query_knowledge,
index_docs,
index_single,
get_knowledge_stats,
list_indexed_docs,
delete_doc,
)
def print_answer(result: Dict):
answer = result.get("answer", "")
sources = result.get("sources", [])
chunks = result.get("chunks_used", 0)
if RICH:
console.print(Panel(
Markdown(answer),
title="[bold cyan]Answer[/bold cyan]",
border_style="cyan",
))
if sources:
console.print(f"[dim]Sources ({chunks} chunks): {', '.join(sources)}[/dim]")
else:
print("\n" + "="*60)
print("ANSWER:")
print(answer)
if sources:
print(f"\nSources ({chunks} chunks): {', '.join(sources)}")
print("="*60 + "\n")
def cmd_index(args):
docs_dir = args.dir or "./documents"
print(f"[Knowledge Agent] Indexing documents in: {docs_dir}")
index_docs(docs_dir=docs_dir, force=args.force)
def cmd_ask(args):
question = " ".join(args.question)
if RICH:
console.print(f"\n[bold]Question:[/bold] {question}")
else:
print(f"\nQuestion: {question}")
result = query_knowledge(question, top_k=args.top_k)
print_answer(result)
def cmd_chat(args):
"""Interactive REPL β€” keeps asking questions until the user exits."""
if RICH:
console.print(Panel(
"[bold cyan]Knowledge Agent β€” Interactive Chat[/bold cyan]\n"
"Ask anything about your documents.\n"
"Type [yellow]exit[/yellow] or [yellow]quit[/yellow] to stop.",
border_style="cyan",
))
else:
print("\n" + "="*60)
print(" Knowledge Agent β€” Interactive Chat")
print(" Type 'exit' to quit.")
print("="*60 + "\n")
while True:
try:
if RICH:
question = console.input("[bold green]You:[/bold green] ").strip()
else:
question = input("You: ").strip()
except (KeyboardInterrupt, EOFError):
print("\nGoodbye!")
break
if not question:
continue
if question.lower() in ("exit", "quit", "q", "bye"):
print("Goodbye!")
break
# Special commands in chat mode
if question.lower() == "/stats":
cmd_stats(args)
continue
if question.lower() == "/list":
cmd_list(args)
continue
if question.lower().startswith("/index "):
path = question[7:].strip()
index_single(path)
continue
result = query_knowledge(question, top_k=args.top_k if hasattr(args, "top_k") else 5)
print_answer(result)
def cmd_list(args):
docs = list_indexed_docs()
if not docs:
print("No documents indexed yet.")
return
if RICH:
table = Table(title="Indexed Documents", border_style="cyan")
table.add_column("File", style="bold")
table.add_column("Status")
table.add_column("Path", style="dim")
for d in docs:
status = "[green]βœ“ exists[/green]" if d["exists"] else "[red]βœ— missing[/red]"
table.add_row(d["name"], status, d["path"])
console.print(table)
else:
print(f"\n{'File':<40} {'Status':<12} Path")
print("-" * 80)
for d in docs:
status = "βœ“ exists" if d["exists"] else "βœ— missing"
print(f"{d['name']:<40} {status:<12} {d['path']}")
def cmd_stats(args):
stats = get_knowledge_stats()
if RICH:
console.print(Panel(
f"[bold]Total chunks:[/bold] {stats['total_chunks']}\n"
f"[bold]Indexed files:[/bold] {stats['indexed_files']}\n"
f"[bold]Embed model:[/bold] {stats['embed_model']}\n"
f"[bold]ChromaDB dir:[/bold] {stats['chroma_dir']}",
title="[cyan]Knowledge Base Stats[/cyan]",
border_style="cyan",
))
else:
print("\nKnowledge Base Stats:")
for k, v in stats.items():
print(f" {k}: {v}")
def cmd_delete(args):
filename = args.filename
confirm = input(f"Delete all chunks for '{filename}'? [y/N] ").strip().lower()
if confirm == "y":
delete_doc(filename)
print(f"Deleted: {filename}")
else:
print("Cancelled.")
# ── Argument parser ───────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(
prog="knowledge",
description="Personal Knowledge Agent β€” RAG over your documents",
)
subparsers = parser.add_subparsers(dest="command", required=True)
# index
p_index = subparsers.add_parser("index", help="Index documents into ChromaDB")
p_index.add_argument("--dir", default=None, help="Documents directory (default: ./documents)")
p_index.add_argument("--force", action="store_true", help="Force re-index all files")
p_index.set_defaults(func=cmd_index)
# ask
p_ask = subparsers.add_parser("ask", help="Ask a one-shot question")
p_ask.add_argument("question", nargs="+", help="Your question (in quotes or multiple words)")
p_ask.add_argument("--top-k", dest="top_k", type=int, default=5,
help="Number of chunks to retrieve (default: 5)")
p_ask.set_defaults(func=cmd_ask)
# chat
p_chat = subparsers.add_parser("chat", help="Interactive chat loop")
p_chat.add_argument("--top-k", dest="top_k", type=int, default=5)
p_chat.set_defaults(func=cmd_chat)
# list
p_list = subparsers.add_parser("list", help="List indexed documents")
p_list.set_defaults(func=cmd_list)
# stats
p_stats = subparsers.add_parser("stats", help="Show knowledge base statistics")
p_stats.set_defaults(func=cmd_stats)
# delete
p_del = subparsers.add_parser("delete", help="Remove a document from the index")
p_del.add_argument("filename", help="Filename to delete (e.g. report.pdf)")
p_del.set_defaults(func=cmd_delete)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()