File size: 7,501 Bytes
58b74a0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | """
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() |