cloud ask: human-rendered output by default, JSON=1 to switch
Mirrors `make query` ergonomics:
- default = pretty terminal layout (audit_label, sources w/ roles,
bucket stats footer)
- JSON=1 (or --json) = full machine-readable record
_render_cloud_ask_human shares the audit-label primitive
(_render_audit_label) with the local query renderer so the
HYBRID/STRICT/UNGROUNDED tokens map to the same four-rung ladder
labels in lattice modes (POINTER-LINKED / ANCHOR-WARRANTED / ...).
Bucket-direct path doesn't emit warrant-tail / run-DAG /
retrieval-purity so those sections are trimmed.
Output footer adds 'bucket: N HTTP requests · KB · endpoint / model'
so the operator can see network cost + LLM identity inline.
This commit is contained in:
parent
462f639163
commit
cb8c1deff2
2 changed files with 70 additions and 6 deletions
7
Makefile
7
Makefile
|
|
@ -1572,13 +1572,14 @@ cloud-fetch-chunk: bootstrap ## fetch + hash-verify one chunk from a bucket [LEA
|
|||
@test -n "$(BLOB_BASE)" || { echo 'BLOB_BASE required (per-chunk blobs/<hash> base URL)'; exit 2; }
|
||||
@$(ARBORIST) cloud fetch-chunk "$(LEAF_HASH)" --blob-base "$(BLOB_BASE)"
|
||||
|
||||
cloud-ask: bootstrap ## bucket-direct end-to-end: FTS → LLM → verify [Q="..." SHARD_URL=... TOP_K=N MAX_CONTEXT=N CACHE_MB=N]
|
||||
@test -n "$(Q)" || { echo 'usage: make cloud-ask Q="your question" [SHARD_URL=...] [TOP_K=4] [MAX_CONTEXT=24000] [CACHE_MB=64]'; exit 2; }
|
||||
cloud-ask: bootstrap ## bucket-direct end-to-end: FTS → LLM → verify [Q="..." JSON=1 SHARD_URL=... TOP_K=N MAX_CONTEXT=N CACHE_MB=N]
|
||||
@test -n "$(Q)" || { echo 'usage: make cloud-ask Q="your question" [JSON=1] [SHARD_URL=...] [TOP_K=4] [MAX_CONTEXT=24000] [CACHE_MB=64]'; exit 2; }
|
||||
@echo "# shard: $(SHARD_URL)" >&2
|
||||
@$(ARBORIST) cloud ask '$(Q)' --shard-url "$(SHARD_URL)" \
|
||||
--top-k $(or $(TOP_K),4) \
|
||||
--max-context-chars $(or $(MAX_CONTEXT),24000) \
|
||||
--cache-mb $(or $(CACHE_MB),64)
|
||||
--cache-mb $(or $(CACHE_MB),64) \
|
||||
$(if $(JSON),--json,)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Head-to-head: local query (BURN=1) vs cloud-ask, same question, same data.
|
||||
|
|
|
|||
|
|
@ -7290,6 +7290,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
"--model", default=None,
|
||||
help="LLM model id (default: $ARBORIST_LLM_MODEL or Hermes-3-8B).",
|
||||
)
|
||||
cloud_ask.add_argument(
|
||||
"--json", action="store_true",
|
||||
help="emit the full result as JSON. Default: human-rendered "
|
||||
"(matches `arborist query` output).",
|
||||
)
|
||||
cloud_ask.set_defaults(func=_cmd_cloud_ask)
|
||||
|
||||
return p
|
||||
|
|
@ -7550,7 +7555,8 @@ def _cmd_cloud_ask(args: argparse.Namespace) -> int:
|
|||
finally:
|
||||
client.close()
|
||||
|
||||
print(json.dumps({
|
||||
elapsed = _time.time() - t0
|
||||
result = {
|
||||
"answer_text": answer,
|
||||
"audit_mode": verdict["audit_mode"],
|
||||
"verifier_method": verdict["verifier_method"],
|
||||
|
|
@ -7559,13 +7565,70 @@ def _cmd_cloud_ask(args: argparse.Namespace) -> int:
|
|||
"unverified_quotes": verdict.get("unverified_quotes", []),
|
||||
"sources": sources,
|
||||
"stats": client.stats(),
|
||||
"timing_s": round(_time.time() - t0, 3),
|
||||
"timing_s": round(elapsed, 3),
|
||||
"endpoint": endpoint,
|
||||
"model": model,
|
||||
}, indent=2, ensure_ascii=False))
|
||||
}
|
||||
if getattr(args, "json", False):
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
else:
|
||||
print(_render_cloud_ask_human(result, args.question))
|
||||
return 0
|
||||
|
||||
|
||||
def _render_cloud_ask_human(result: dict, question: str) -> str:
|
||||
"""Pretty-print a cloud-ask result. Mirrors `_render_query_human`'s
|
||||
layout but trims sections that the bucket-direct path doesn't
|
||||
produce (warrant tails, run-DAG, retrieval-purity)."""
|
||||
audit = result.get("audit_mode") or "UNGROUNDED"
|
||||
method = result.get("verifier_method") or "?"
|
||||
n_quotes = result.get("n_quotes") or 0
|
||||
n_verified = result.get("n_verified") or 0
|
||||
elapsed_s = result.get("timing_s") or 0.0
|
||||
label = _render_audit_label(audit, method, [])
|
||||
stats = result.get("stats") or {}
|
||||
cache_stats = stats.get("cache") or {}
|
||||
http_reqs = stats.get("http_requests")
|
||||
bytes_fetched = cache_stats.get("bytes_fetched") or 0
|
||||
|
||||
lines: list[str] = []
|
||||
lines.append(question)
|
||||
lines.append(
|
||||
f" {label} {n_verified}/{n_quotes} {elapsed_s:.2f}s (bucket-direct)"
|
||||
)
|
||||
lines.append("")
|
||||
lines.append(result.get("answer_text") or "")
|
||||
lines.append("")
|
||||
|
||||
sources = result.get("sources") or []
|
||||
if sources:
|
||||
lines.append(f"sources ({len(sources)}):")
|
||||
for i, s in enumerate(sources, start=1):
|
||||
uri = s.get("document_uri", "")
|
||||
title = (s.get("title") or "").strip() or _short_path(uri)
|
||||
role = s.get("source_role")
|
||||
role_part = f" — {role}" if role else ""
|
||||
lines.append(
|
||||
f" [{i}] {title}{role_part} — {_strip_scheme(uri)}"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
unverified = result.get("unverified_quotes") or []
|
||||
if unverified:
|
||||
lines.append(f"unverified ({len(unverified)}):")
|
||||
for q in unverified[:5]:
|
||||
lines.append(f' - "{q[:140]}{"…" if len(q) > 140 else ""}"')
|
||||
lines.append("")
|
||||
|
||||
bytes_kb = bytes_fetched / 1024.0
|
||||
lines.append(
|
||||
f"bucket: {http_reqs} HTTP requests · {bytes_kb:.1f} KB · "
|
||||
f"{result.get('endpoint','')} / {result.get('model','')}"
|
||||
)
|
||||
lines.append(" <run with --json for full record>")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _cmd_cloud_fetch_chunk(args: argparse.Namespace) -> int:
|
||||
from arborist.merkle import hash_leaf
|
||||
client = _make_bucket_client(args)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue