Skip to content
Open
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
105 changes: 89 additions & 16 deletions src/rag_python/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,7 @@ def _make_parser() -> argparse.ArgumentParser:
"optionally stream tokens and show sources."
),
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"examples:\n"
' rag-python query "How many days of annual leave?"\n'
" rag-python query \"PTO policy\" --stream -v\n"
' rag-python query "benefits" --retriever hybrid --metadata-filter \'{"filename": "hr.pdf"}\''
),
# Epilog is populated below (after new flags) with expanded Agent-friendly examples.
)
q.add_argument(
"question",
Expand All @@ -183,13 +178,58 @@ def _make_parser() -> argparse.ArgumentParser:
action="store_true",
help="After the answer, print evaluation scores and top source paths",
)
q.add_argument(
"-q",
"--quiet",
action="store_true",
help=(
"Only print the answer text; suppress evaluation and sources trailers. "
"Useful for scripts and CI pipelines."
),
)
q.add_argument(
"-f",
"--output-format",
default="text",
choices=["text", "json", "json-pretty"],
metavar="FMT",
help=(
"Output format (default: text). Use 'json' for structured output — "
"designed for calling the CLI as an Agent tool via subprocess with "
"json.loads(). 'json-pretty' adds indentation for human inspection."
),
)
q.add_argument(
"--with-sources",
action="store_true",
help=(
"Print the top-5 sources trailer without requiring the full "
"-v/--verbose evaluation section."
),
)
_add_provider_args(q)
_add_search_args(q)

# Epilog: Agent-friendly flags. Long strings split to stay within line-length=100.
q.epilog = (
"examples:\n"
' rag-python query "How many days of annual leave?"\n'
" rag-python query \"PTO policy\" --stream -v\n"
' rag-python query "benefits" --retriever hybrid'
' --metadata-filter \'{"filename": "hr.pdf"}\'\n'
" rag-python query \"leave policy\" -q"
" # answer only, script-friendly\n"
' rag-python query "payroll url" -f json'
' --quiet # Agent tool: structured JSON\n'
" rag-python query \"refund rule\" --with-sources"
" # sources section, no eval noise\n"
)
docs = sub.add_parser(
"docs",
help="Show user documentation in the terminal",
description="Print built-in help topics. Full docs: https://github.com/RaghavOG/rag-python/tree/main/docs",
description=(
"Print built-in help topics. "
"Full docs: https://github.com/RaghavOG/rag-python/tree/main/docs"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="topics: " + ", ".join(list_topics()),
)
Expand Down Expand Up @@ -238,28 +278,61 @@ def main(argv: list[str] | None = None) -> None:
retriever=retriever or rag.config.search.retriever,
metadata_filter=args.metadata_filter or rag.config.search.metadata_filter,
)
fmt = args.output_format # "text" (default) | "json" | "json-pretty"

# -------- Highest priority: structured output (Agent / tool use) --------
# When the caller explicitly requests JSON, we always return the full
# RAGAnswer shape as a single JSON object (stream tokens are discarded
# because JSON output is a final snapshot by definition).
if fmt in ("json", "json-pretty"):
ans = rag.query(question, search=search)
payload = {
"text": ans.text,
"sources": ans.sources,
"evaluation": ans.evaluation,
"retried": ans.retried,
}
if fmt == "json":
print(json.dumps(payload, ensure_ascii=False))
else:
print(json.dumps(payload, ensure_ascii=False, indent=2))
return

# ---------------------- Default: text (human-readable) ----------------------
quiet = args.quiet
show_sources = args.with_sources or args.verbose
show_evaluation = args.verbose

def _print_sources_trailer(sources: list[dict]) -> None:
print("\n--- sources ---")
for s in sources[:5]:
meta = s.get("metadata", {}) or {}
print(meta.get("source", ""), "score:", s.get("score"))

if args.stream:
stream = rag.query_stream(question, search=search)
for token in stream:
print(token, end="", flush=True)
print()
if quiet:
return
result = stream.result
if args.verbose:
if show_evaluation:
print("\n--- evaluation ---")
print(result.evaluation)
print("\n--- sources ---")
for s in result.sources[:5]:
print(s.get("metadata", {}).get("source", ""), "score:", s.get("score"))
if show_sources:
_print_sources_trailer(result.sources)
return

ans = rag.query(question, search=search)
print(ans.text)
if args.verbose:
if quiet:
return
if show_evaluation:
print("\n--- evaluation ---")
print(ans.evaluation)
print("\n--- sources ---")
for s in ans.sources[:5]:
print(s.get("metadata", {}).get("source", ""), "score:", s.get("score"))
if show_sources:
_print_sources_trailer(ans.sources)


if __name__ == "__main__":
Expand Down
Loading