File size: 5,335 Bytes
ddc12cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
COLLISION Phase 99 — Production Grounded Answering CLI.

Provides unified command-line entrypoints backed by the shared Application Service Layer:
- python -m collision ask "<question>" [--mode AUTO|LOCAL|WEB|HYBRID|MODEL] [--verbose] [--json]
- python -m collision chat [--mode AUTO|LOCAL|WEB|HYBRID|MODEL] [--verbose]
"""

import os
import sys
import argparse
import json
from typing import Optional

PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if PROJECT_ROOT not in sys.path:
    sys.path.insert(0, PROJECT_ROOT)

from collision.service import get_collision_service, CollisionService


def handle_ask(args):
    service = get_collision_service()
    question = args.question.strip() if args.question else ""

    result = service.ask(
        question=question,
        mode=args.mode.upper(),
        include_sources=True,
        include_claims=True
    )

    if args.json:
        print(json.dumps(result, indent=2))
        return

    # User-facing structured presentation
    print("\nAnswer:")
    print(result.get("answer", "No answer produced."))

    sources = result.get("sources", [])
    if sources:
        print("\nSources:")
        for s in sources:
            title = s.get("title", "Document")
            url = s.get("url", "")
            print(f"- {title} ({url})")

    if args.verbose:
        print("\nExecution Diagnostics:")
        print(f"  Status     : {result.get('status')}")
        print(f"  Route Mode : {result.get('mode')}")
        print(f"  Confidence : {result.get('confidence', 0.0):.2f}")
        lat = result.get("latency", {})
        print(f"  Latency    : {lat.get('total_ms', 0.0):.2f} ms (routing: {lat.get('routing_ms', 0.0):.1f}ms, ret: {lat.get('retrieval_ms', 0.0):.1f}ms, gen: {lat.get('generation_ms', 0.0):.1f}ms, ver: {lat.get('verification_ms', 0.0):.1f}ms)")
        claims = result.get("claims", [])
        if claims:
            print("  Claims:")
            for c in claims:
                print(f"    - [{c.get('support_status')}] {c.get('text')}")
    print()


def handle_chat(args):
    service = get_collision_service()
    selected_mode = args.mode.upper()

    print("=" * 65)
    print("      COLLISION — Production Grounded Answer Chat (Phase 99)")
    print("=" * 65)
    print("Commands:")
    print("  /exit, /quit     - Exit session")
    print("  /mode <MODE>     - Set mode (AUTO, LOCAL, WEB, HYBRID, MODEL)")
    print("  /verbose         - Toggle verbose diagnostics")
    print("=" * 65 + "\n")

    verbose = args.verbose

    while True:
        try:
            user_input = input("User:\n").strip()
            if not user_input:
                continue

            if user_input.lower() in ("/exit", "/quit"):
                print("Goodbye!")
                break
            elif user_input.lower() == "/verbose":
                verbose = not verbose
                print(f"[Verbose diagnostics: {'ON' if verbose else 'OFF'}]\n")
                continue
            elif user_input.lower().startswith("/mode "):
                parts = user_input.split()
                if len(parts) > 1:
                    selected_mode = parts[1].upper()
                    print(f"[Mode set to {selected_mode}]\n")
                continue

            result = service.ask(question=user_input, mode=selected_mode)

            print(f"\nAssistant:\n{result.get('answer', '')}")
            sources = result.get("sources", [])
            if sources:
                print("\nSources:")
                for s in sources:
                    print(f"- {s.get('title', 'Doc')} ({s.get('url', '')})")

            if verbose:
                lat = result.get("latency", {})
                print(f"\n[Diagnostics] Status: {result.get('status')} | Mode: {result.get('mode')} | Latency: {lat.get('total_ms', 0.0):.1f}ms | Confidence: {result.get('confidence', 0.0):.2f}")
            print()

        except (KeyboardInterrupt, EOFError):
            print("\nSession ended.")
            break


def main():
    parser = argparse.ArgumentParser(prog="collision", description="COLLISION Production Grounded Answering CLI")
    subparsers = parser.add_subparsers(dest="command")

    # 'ask' command
    ask_parser = subparsers.add_parser("ask", help="Ask a grounded question")
    ask_parser.add_argument("question", type=str, nargs="?", default="", help="Question to ask")
    ask_parser.add_argument("--mode", type=str, default="AUTO", choices=["AUTO", "LOCAL", "WEB", "HYBRID", "MODEL"], help="Routing mode")
    ask_parser.add_argument("--verbose", action="store_true", help="Show execution diagnostics")
    ask_parser.add_argument("--json", action="store_true", help="Output raw JSON matching public API response schema")

    # 'chat' command
    chat_parser = subparsers.add_parser("chat", help="Start an interactive chat session")
    chat_parser.add_argument("--mode", type=str, default="AUTO", choices=["AUTO", "LOCAL", "WEB", "HYBRID", "MODEL"], help="Default routing mode")
    chat_parser.add_argument("--verbose", action="store_true", help="Show execution diagnostics")

    args = parser.parse_args()

    if args.command == "ask":
        handle_ask(args)
    elif args.command == "chat":
        handle_chat(args)
    else:
        parser.print_help()


if __name__ == "__main__":
    main()